diff --git a/bin/meshroom_photogrammetry b/bin/meshroom_photogrammetry
index 556be74fdd..3abfd9d19a 100755
--- a/bin/meshroom_photogrammetry
+++ b/bin/meshroom_photogrammetry
@@ -72,7 +72,7 @@ args = parser.parse_args()
def getOnlyNodeOfType(g, nodeType):
""" Helper function to get a node of 'nodeType' in the graph 'g' and raise if no or multiple candidates. """
- nodes = g.nodesByType(nodeType)
+ nodes = g.nodesOfType(nodeType)
if len(nodes) != 1:
raise RuntimeError("meshroom_photogrammetry requires a pipeline graph with exactly one '{}' node, {} found."
.format(nodeType, len(nodes)))
@@ -163,10 +163,10 @@ with multiview.GraphModification(graph):
raise ValueError('Invalid param override: ' + str(p))
node, t, param, value = result.groups()
if t == ':':
- nodesByType = graph.nodesByType(node)
- if not nodesByType:
+ nodesOfType = graph.nodesOfType(node)
+ if not nodesOfType:
raise ValueError('No node with the type "{}" in the scene.'.format(node))
- for n in nodesByType:
+ for n in nodesOfType:
print('Overrides {node}.{param}={value}'.format(node=node, param=param, value=value))
n.attribute(param).value = value
elif t == '.':
@@ -178,7 +178,7 @@ with multiview.GraphModification(graph):
# setup DepthMap downscaling
if args.scale > 0:
- for node in graph.nodesByType('DepthMap'):
+ for node in graph.nodesOfType('DepthMap'):
node.downscale.value = args.scale
# setup cache directory
diff --git a/meshroom/core/attribute.py b/meshroom/core/attribute.py
index c0651de274..8291580ba6 100644
--- a/meshroom/core/attribute.py
+++ b/meshroom/core/attribute.py
@@ -93,6 +93,9 @@ def getName(self):
def getType(self):
return self.attributeDesc.__class__.__name__
+ def _isReadOnly(self):
+ return not self._isOutput and self.node.isCompatibilityNode
+
def getBaseType(self):
return self.getType()
@@ -262,6 +265,7 @@ def updateInternals(self):
label = Property(str, getLabel, constant=True)
type = Property(str, getType, constant=True)
baseType = Property(str, getType, constant=True)
+ isReadOnly = Property(bool, _isReadOnly, constant=True)
desc = Property(desc.Attribute, lambda self: self.attributeDesc, constant=True)
valueChanged = Signal()
value = Property(Variant, _get_value, _set_value, notify=valueChanged)
diff --git a/meshroom/core/graph.py b/meshroom/core/graph.py
index 27d2e56e94..d2703b38fb 100644
--- a/meshroom/core/graph.py
+++ b/meshroom/core/graph.py
@@ -548,7 +548,7 @@ def sortNodesByIndex(nodes):
"""
return sorted(nodes, key=lambda x: Graph.getNodeIndexFromName(x.name))
- def nodesByType(self, nodeType, sortedByIndex=True):
+ def nodesOfType(self, nodeType, sortedByIndex=True):
"""
Returns all Nodes of the given nodeType.
diff --git a/meshroom/core/node.py b/meshroom/core/node.py
index 00646e6c6e..42c2bc5713 100644
--- a/meshroom/core/node.py
+++ b/meshroom/core/node.py
@@ -58,12 +58,12 @@ class ExecMode(Enum):
EXTERN = 2
-class StatusData:
+class StatusData(BaseObject):
"""
"""
dateTimeFormatting = '%Y-%m-%d %H:%M:%S.%f'
- def __init__(self, nodeName, nodeType, packageName, packageVersion):
+ def __init__(self, nodeName='', nodeType='', packageName='', packageVersion=''):
self.status = Status.NONE
self.execMode = ExecMode.NONE
self.nodeName = nodeName
@@ -79,6 +79,11 @@ def __init__(self, nodeName, nodeType, packageName, packageVersion):
self.hostname = ""
self.sessionUid = meshroom.core.sessionUid
+ def merge(self, other):
+ self.startDateTime = min(self.startDateTime, other.startDateTime)
+ self.endDateTime = max(self.endDateTime, other.endDateTime)
+ self.elapsedTime += other.elapsedTime
+
def reset(self):
self.status = Status.NONE
self.execMode = ExecMode.NONE
@@ -112,8 +117,12 @@ def toDict(self):
return d
def fromDict(self, d):
- self.status = getattr(Status, d.get('status', ''), Status.NONE)
- self.execMode = getattr(ExecMode, d.get('execMode', ''), ExecMode.NONE)
+ self.status = d.get('status', Status.NONE)
+ if not isinstance(self.status, Status):
+ self.status = Status[self.status]
+ self.execMode = d.get('execMode', ExecMode.NONE)
+ if not isinstance(self.execMode, ExecMode):
+ self.execMode = ExecMode[self.execMode]
self.nodeName = d.get('nodeName', '')
self.nodeType = d.get('nodeType', '')
self.packageName = d.get('packageName', '')
@@ -236,7 +245,7 @@ def __init__(self, node, range, parent=None):
self.node = node
self.range = range
self.logManager = LogManager(self)
- self.status = StatusData(node.name, node.nodeType, node.packageName, node.packageVersion)
+ self._status = StatusData(node.name, node.nodeType, node.packageName, node.packageVersion)
self.statistics = stats.Statistics()
self.statusFileLastModTime = -1
self._subprocess = None
@@ -258,7 +267,7 @@ def name(self):
@property
def statusName(self):
- return self.status.status.name
+ return self._status.status.name
@property
def logger(self):
@@ -266,24 +275,24 @@ def logger(self):
@property
def execModeName(self):
- return self.status.execMode.name
+ return self._status.execMode.name
def updateStatusFromCache(self):
"""
Update node status based on status file content/existence.
"""
statusFile = self.statusFile
- oldStatus = self.status.status
+ oldStatus = self._status.status
# No status file => reset status to Status.None
if not os.path.exists(statusFile):
self.statusFileLastModTime = -1
- self.status.reset()
+ self._status.reset()
else:
with open(statusFile, 'r') as jsonFile:
statusData = json.load(jsonFile)
- self.status.fromDict(statusData)
+ self._status.fromDict(statusData)
self.statusFileLastModTime = os.path.getmtime(statusFile)
- if oldStatus != self.status.status:
+ if oldStatus != self._status.status:
self.statusChanged.emit()
@property
@@ -311,7 +320,7 @@ def saveStatusFile(self):
"""
Write node status on disk.
"""
- data = self.status.toDict()
+ data = self._status.toDict()
statusFilepath = self.statusFile
folder = os.path.dirname(statusFilepath)
if not os.path.exists(folder):
@@ -322,16 +331,16 @@ def saveStatusFile(self):
renameWritingToFinalPath(statusFilepathWriting, statusFilepath)
def upgradeStatusTo(self, newStatus, execMode=None):
- if newStatus.value <= self.status.status.value:
- print('WARNING: downgrade status on node "{}" from {} to {}'.format(self.name, self.status.status,
- newStatus))
+ if newStatus.value <= self._status.status.value:
+ logging.warning('Downgrade status on node "{}" from {} to {}'.format(self.name, self._status.status,
+ newStatus))
if newStatus == Status.SUBMITTED:
- self.status = StatusData(self.node.name, self.node.nodeType, self.node.packageName, self.node.packageVersion)
+ self._status = StatusData(self.node.name, self.node.nodeType, self.node.packageName, self.node.packageVersion)
if execMode is not None:
- self.status.execMode = execMode
+ self._status.execMode = execMode
self.execModeNameChanged.emit()
- self.status.status = newStatus
+ self._status.status = newStatus
self.saveStatusFile()
self.statusChanged.emit()
@@ -360,24 +369,24 @@ def saveStatistics(self):
renameWritingToFinalPath(statisticsFilepathWriting, statisticsFilepath)
def isAlreadySubmitted(self):
- return self.status.status in (Status.SUBMITTED, Status.RUNNING)
+ return self._status.status in (Status.SUBMITTED, Status.RUNNING)
def isAlreadySubmittedOrFinished(self):
- return self.status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS)
+ return self._status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS)
def isFinishedOrRunning(self):
- return self.status.status in (Status.SUCCESS, Status.RUNNING)
+ return self._status.status in (Status.SUCCESS, Status.RUNNING)
def isStopped(self):
- return self.status.status == Status.STOPPED
+ return self._status.status == Status.STOPPED
def process(self, forceCompute=False):
- if not forceCompute and self.status.status == Status.SUCCESS:
- print("Node chunk already computed:", self.name)
+ if not forceCompute and self._status.status == Status.SUCCESS:
+ logging.info("Node chunk already computed: {}".format(self.name))
return
global runningProcesses
runningProcesses[self.name] = self
- self.status.initStartCompute()
+ self._status.initStartCompute()
startTime = time.time()
self.upgradeStatusTo(Status.RUNNING)
self.statThread = stats.StatisticsThread(self)
@@ -385,16 +394,16 @@ def process(self, forceCompute=False):
try:
self.node.nodeDesc.processChunk(self)
except Exception as e:
- if self.status.status != Status.STOPPED:
+ if self._status.status != Status.STOPPED:
self.upgradeStatusTo(Status.ERROR)
raise
except (KeyboardInterrupt, SystemError, GeneratorExit) as e:
self.upgradeStatusTo(Status.STOPPED)
raise
finally:
- self.status.initEndCompute()
- self.status.elapsedTime = time.time() - startTime
- print(' - elapsed time:', self.status.elapsedTimeStr)
+ self._status.initEndCompute()
+ self._status.elapsedTime = time.time() - startTime
+ logging.info(' - elapsed time: {}'.format(self._status.elapsedTimeStr))
# ask and wait for the stats thread to stop
self.statThread.stopRequest()
self.statThread.join()
@@ -408,9 +417,10 @@ def stopProcess(self):
self.node.nodeDesc.stopProcess(self)
def isExtern(self):
- return self.status.execMode == ExecMode.EXTERN
+ return self._status.execMode == ExecMode.EXTERN
statusChanged = Signal()
+ status = Property(Variant, lambda self: self._status, notify=statusChanged)
statusName = Property(str, statusName.fget, notify=statusChanged)
execModeNameChanged = Signal()
execModeName = Property(str, execModeName.fget, notify=execModeNameChanged)
@@ -422,7 +432,7 @@ def isExtern(self):
statisticsFile = Property(str, statisticsFile.fget, notify=nodeFolderChanged)
nodeName = Property(str, lambda self: self.node.name, constant=True)
- statusNodeName = Property(str, lambda self: self.status.nodeName, constant=True)
+ statusNodeName = Property(str, lambda self: self._status.nodeName, constant=True)
# simple structure for storing node position
@@ -837,6 +847,27 @@ def getGlobalStatus(self):
return Status.NONE
+ @Slot(result=StatusData)
+ def getFusedStatus(self):
+ fusedStatus = StatusData()
+ if self._chunks:
+ fusedStatus.fromDict(self._chunks[0].status.toDict())
+ for chunk in self._chunks[1:]:
+ fusedStatus.merge(chunk.status)
+ fusedStatus.status = self.getGlobalStatus()
+ return fusedStatus
+
+ @Slot(result=StatusData)
+ def getRecursiveFusedStatus(self):
+ fusedStatus = self.getFusedStatus()
+ nodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
+ for node in nodes:
+ fusedStatus.merge(node.fusedStatus)
+ return fusedStatus
+
+ def _isCompatibilityNode(self):
+ return False
+
@property
def globalExecMode(self):
return self._chunks.at(0).execModeName
@@ -1000,6 +1031,11 @@ def canBeCanceled(self):
size = Property(int, getSize, notify=sizeChanged)
globalStatusChanged = Signal()
globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged)
+ fusedStatus = Property(StatusData, getFusedStatus, notify=globalStatusChanged)
+ elapsedTime = Property(float, lambda self: self.getFusedStatus().elapsedTime, notify=globalStatusChanged)
+ recursiveElapsedTime = Property(float, lambda self: self.getRecursiveFusedStatus().elapsedTime, notify=globalStatusChanged)
+ isCompatibilityNode = Property(bool, lambda self: self._isCompatibilityNode(), constant=True) # need lambda to evaluate the virtual function
+
globalExecModeChanged = Signal()
globalExecMode = Property(str, globalExecMode.fget, notify=globalExecModeChanged)
isComputed = Property(bool, _isComputed, notify=globalStatusChanged)
@@ -1135,6 +1171,9 @@ def __init__(self, nodeType, nodeDict, position=None, issue=CompatibilityIssue.U
for i in range(self.splitCount)
])
+ def _isCompatibilityNode(self):
+ return True
+
@staticmethod
def attributeDescFromValue(attrName, value, isOutput):
"""
diff --git a/meshroom/multiview.py b/meshroom/multiview.py
index 76ccce6ca8..776338e20b 100644
--- a/meshroom/multiview.py
+++ b/meshroom/multiview.py
@@ -178,11 +178,11 @@ def panoramaFisheyeHdr(inputImages=None, inputViewpoints=None, inputIntrinsics=N
graph = Graph('PanoramaFisheyeHDR')
with GraphModification(graph):
panoramaHdr(inputImages, inputViewpoints, inputIntrinsics, output, graph)
- for panoramaInit in graph.nodesByType("PanoramaInit"):
+ for panoramaInit in graph.nodesOfType("PanoramaInit"):
panoramaInit.attribute("useFisheye").value = True
# when using fisheye images, the overlap between images can be small
# and thus requires many features to get enough correspondances for cameras estimation
- for featureExtraction in graph.nodesByType("FeatureExtraction"):
+ for featureExtraction in graph.nodesOfType("FeatureExtraction"):
featureExtraction.attribute("describerPreset").value = 'high'
return graph
diff --git a/meshroom/nodes/aliceVision/LdrToHdrMerge.py b/meshroom/nodes/aliceVision/LdrToHdrMerge.py
index af6f043503..e1b1c88cfb 100644
--- a/meshroom/nodes/aliceVision/LdrToHdrMerge.py
+++ b/meshroom/nodes/aliceVision/LdrToHdrMerge.py
@@ -104,6 +104,15 @@ class LdrToHdrMerge(desc.CommandLineNode):
advanced=True,
enabled= lambda node: node.byPass.enabled and not node.byPass.value,
),
+ desc.BoolParam(
+ name='enableHighlight',
+ label='Enable Highlight',
+ description="Enable highlights correction.",
+ value=True,
+ uid=[0],
+ group='user', # not used directly on the command line
+ enabled= lambda node: node.byPass.enabled and not node.byPass.value,
+ ),
desc.FloatParam(
name='highlightCorrectionFactor',
label='Highlights Correction',
@@ -115,7 +124,7 @@ class LdrToHdrMerge(desc.CommandLineNode):
value=1.0,
range=(0.0, 1.0, 0.01),
uid=[0],
- enabled= lambda node: node.byPass.enabled and not node.byPass.value,
+ enabled= lambda node: node.enableHighlight.enabled and node.enableHighlight.value,
),
desc.FloatParam(
name='highlightTargetLux',
@@ -138,7 +147,7 @@ class LdrToHdrMerge(desc.CommandLineNode):
value=120000.0,
range=(1000.0, 150000.0, 1.0),
uid=[0],
- enabled= lambda node: node.byPass.enabled and not node.byPass.value and node.highlightCorrectionFactor.value != 0,
+ enabled= lambda node: node.enableHighlight.enabled and node.enableHighlight.value and node.highlightCorrectionFactor.value != 0,
),
desc.ChoiceParam(
name='storageDataType',
diff --git a/meshroom/nodes/aliceVision/PanoramaEstimation.py b/meshroom/nodes/aliceVision/PanoramaEstimation.py
index b89ae370da..1cc147fa92 100644
--- a/meshroom/nodes/aliceVision/PanoramaEstimation.py
+++ b/meshroom/nodes/aliceVision/PanoramaEstimation.py
@@ -98,6 +98,21 @@ class PanoramaEstimation(desc.CommandLineNode):
uid=[0],
advanced=True,
),
+ desc.BoolParam(
+ name='rotationAveragingWeighting',
+ label='Rotation Averaging Weighting',
+ description='Rotation averaging weighting based on the number of feature matches.',
+ value=True,
+ uid=[0],
+ advanced=True,
+ ),
+ desc.BoolParam(
+ name='filterMatches',
+ label='Filter Matches',
+ description='Filter Matches',
+ value=False,
+ uid=[0],
+ ),
desc.BoolParam(
name='refine',
label='Refine',
diff --git a/meshroom/nodes/aliceVision/PanoramaInit.py b/meshroom/nodes/aliceVision/PanoramaInit.py
index 740f791a11..c95fe6ff04 100644
--- a/meshroom/nodes/aliceVision/PanoramaInit.py
+++ b/meshroom/nodes/aliceVision/PanoramaInit.py
@@ -27,12 +27,45 @@ class PanoramaInit(desc.CommandLineNode):
value='',
uid=[0],
),
+ desc.ChoiceParam(
+ name='initializeCameras',
+ label='Initialize Cameras',
+ description='Initialize cameras.',
+ value='No',
+ values=['No', 'File', 'Horizontal', 'Horizontal+Zenith', 'Zenith+Horizontal', 'Spherical'],
+ exclusive=True,
+ uid=[0],
+ ),
desc.File(
name='config',
label='Xml Config',
description="XML Data File",
value='',
uid=[0],
+ enabled=lambda node: node.initializeCameras.value == 'File',
+ ),
+ desc.BoolParam(
+ name='yawCW',
+ label='Yaw CW',
+ description="Yaw ClockWise or CounterClockWise",
+ value=1,
+ uid=[0],
+ enabled=lambda node: ('Horizontal' in node.initializeCameras.value) or (node.initializeCameras.value == "Spherical"),
+ ),
+ desc.ListAttribute(
+ elementDesc=desc.IntParam(
+ name='nbViews',
+ label='',
+ description='',
+ value=-1,
+ range=[-1, 20],
+ uid=[0],
+ ),
+ name='nbViewsPerLine',
+ label='Spherical: Nb Views Per Line',
+ description='Number of views per line in Spherical acquisition. Assumes angles from [-90°,+90°] for pitch and [-180°,+180°] for yaw. Use -1 to estimate the number of images automatically.',
+ joinChar=',',
+ enabled=lambda node: node.initializeCameras.value == 'Spherical',
),
desc.ListAttribute(
elementDesc=desc.File(
diff --git a/meshroom/ui/qml/Controls/KeyValue.qml b/meshroom/ui/qml/Controls/KeyValue.qml
new file mode 100644
index 0000000000..0f0f10592e
--- /dev/null
+++ b/meshroom/ui/qml/Controls/KeyValue.qml
@@ -0,0 +1,52 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.3
+import MaterialIcons 2.2
+
+/**
+ * KeyValue allows to create a list of key/value, like a table.
+ */
+Rectangle {
+ property alias key: keyLabel.text
+ property alias value: valueText.text
+
+ color: activePalette.window
+
+ width: parent.width
+ height: childrenRect.height
+
+ RowLayout {
+ width: parent.width
+ Rectangle {
+ anchors.margins: 2
+ color: Qt.darker(activePalette.window, 1.1)
+ // Layout.preferredWidth: sizeHandle.x
+ Layout.minimumWidth: 10.0 * Qt.application.font.pixelSize
+ Layout.maximumWidth: 15.0 * Qt.application.font.pixelSize
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Label {
+ id: keyLabel
+ text: "test"
+ anchors.fill: parent
+ anchors.top: parent.top
+ topPadding: 4
+ leftPadding: 6
+ verticalAlignment: TextEdit.AlignTop
+ elide: Text.ElideRight
+ }
+ }
+ TextArea {
+ id: valueText
+ text: ""
+ anchors.margins: 2
+ Layout.fillWidth: true
+ wrapMode: Label.WrapAtWordBoundaryOrAnywhere
+ textFormat: TextEdit.PlainText
+
+ readOnly: true
+ selectByMouse: true
+ background: Rectangle { anchors.fill: parent; color: Qt.darker(activePalette.window, 1.05) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/meshroom/ui/qml/Controls/qmldir b/meshroom/ui/qml/Controls/qmldir
index a1fc1f6a87..c1e59083b1 100644
--- a/meshroom/ui/qml/Controls/qmldir
+++ b/meshroom/ui/qml/Controls/qmldir
@@ -3,6 +3,7 @@ module Controls
ColorChart 1.0 ColorChart.qml
FloatingPane 1.0 FloatingPane.qml
Group 1.0 Group.qml
+KeyValue 1.0 KeyValue.qml
MessageDialog 1.0 MessageDialog.qml
Panel 1.0 Panel.qml
SearchBar 1.0 SearchBar.qml
diff --git a/meshroom/ui/qml/GraphEditor/AttributePin.qml b/meshroom/ui/qml/GraphEditor/AttributePin.qml
index d7db4cfb9c..705c3e5f4a 100755
--- a/meshroom/ui/qml/GraphEditor/AttributePin.qml
+++ b/meshroom/ui/qml/GraphEditor/AttributePin.qml
@@ -131,7 +131,7 @@ RowLayout {
MouseArea {
id: inputConnectMA
// If an input attribute is connected (isLink), we disable drag&drop
- drag.target: attribute.isLink ? undefined : inputDragTarget
+ drag.target: (attribute.isLink || attribute.isReadOnly) ? undefined : inputDragTarget
drag.threshold: 0
enabled: !root.readOnly
anchors.fill: parent
diff --git a/meshroom/ui/qml/GraphEditor/ChunksListView.qml b/meshroom/ui/qml/GraphEditor/ChunksListView.qml
index f0479726b2..f2ec5544e1 100644
--- a/meshroom/ui/qml/GraphEditor/ChunksListView.qml
+++ b/meshroom/ui/qml/GraphEditor/ChunksListView.qml
@@ -10,57 +10,87 @@ import "common.js" as Common
/**
* ChunkListView
*/
-ListView {
- id: chunksLV
+ColumnLayout {
+ id: root
+ property variant chunks
+ property int currentIndex: 0
+ property variant currentChunk: (chunks && currentIndex >= 0) ? chunks.at(currentIndex) : undefined
- // model: node.chunks
+ onChunksChanged: {
+ // When the list changes, ensure the current index is in the new range
+ if(currentIndex >= chunks.count)
+ currentIndex = chunks.count-1
+ }
- property variant currentChunk: currentItem ? currentItem.chunk : undefined
+ // chunksSummary is in sync with allChunks button (but not directly accessible as it is in a Component)
+ property bool chunksSummary: (currentIndex === -1)
width: 60
- Layout.fillHeight: true
- highlightFollowsCurrentItem: true
- keyNavigationEnabled: true
- focus: true
- currentIndex: 0
- signal changeCurrentChunk(int chunkIndex)
+ ListView {
+ id: chunksLV
+ Layout.fillWidth: true
+ Layout.fillHeight: true
- header: Component {
- Label {
- width: chunksLV.width
- elide: Label.ElideRight
- text: "Chunks"
- padding: 4
- z: 10
- background: Rectangle { color: parent.palette.window }
- }
- }
+ model: root.chunks
- highlight: Component {
- Rectangle {
- color: activePalette.highlight
- opacity: 0.3
- z: 2
+ highlightFollowsCurrentItem: (root.chunksSummary === false)
+ keyNavigationEnabled: true
+ focus: true
+ currentIndex: root.currentIndex
+ onCurrentIndexChanged: {
+ if(chunksLV.currentIndex !== root.currentIndex)
+ {
+ // When the list is resized, the currentIndex is reset to 0.
+ // So here we force it to keep the binding.
+ chunksLV.currentIndex = Qt.binding(function() { return root.currentIndex })
+ }
}
- }
- highlightMoveDuration: 0
- highlightResizeDuration: 0
- delegate: ItemDelegate {
- id: chunkDelegate
- property var chunk: object
- text: index
- width: parent.width
- leftPadding: 8
- onClicked: {
- chunksLV.forceActiveFocus()
- chunksLV.changeCurrentChunk(index)
+ header: Component {
+ Button {
+ id: allChunks
+ text: "Chunks"
+ width: parent.width
+ flat: true
+ checkable: true
+ property bool summaryEnabled: root.chunksSummary
+ checked: summaryEnabled
+ onSummaryEnabledChanged: {
+ checked = summaryEnabled
+ }
+ onClicked: {
+ root.currentIndex = -1
+ checked = true
+ }
+ }
+ }
+ highlight: Component {
+ Rectangle {
+ visible: true // !root.chunksSummary
+ color: activePalette.highlight
+ opacity: 0.3
+ z: 2
+ }
}
- Rectangle {
- width: 4
- height: parent.height
- color: Common.getChunkColor(parent.chunk)
+ highlightMoveDuration: 0
+ highlightResizeDuration: 0
+
+ delegate: ItemDelegate {
+ id: chunkDelegate
+ property var chunk: object
+ text: index
+ width: parent.width
+ leftPadding: 8
+ onClicked: {
+ chunksLV.forceActiveFocus()
+ root.currentIndex = index
+ }
+ Rectangle {
+ width: 4
+ height: parent.height
+ color: Common.getChunkColor(parent.chunk)
+ }
}
}
}
diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml
index 9b6c2e11d9..897d580219 100755
--- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml
+++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml
@@ -222,7 +222,7 @@ Item {
id: edgeMenu
property var currentEdge: null
MenuItem {
- enabled: edgeMenu.currentEdge && !edgeMenu.currentEdge.dst.node.locked
+ enabled: edgeMenu.currentEdge && !edgeMenu.currentEdge.dst.node.locked && !edgeMenu.currentEdge.dst.isReadOnly
text: "Remove"
onTriggered: uigraph.removeEdge(edgeMenu.currentEdge)
}
diff --git a/meshroom/ui/qml/GraphEditor/Node.qml b/meshroom/ui/qml/GraphEditor/Node.qml
index 2825dbbce7..be2a7cf051 100755
--- a/meshroom/ui/qml/GraphEditor/Node.qml
+++ b/meshroom/ui/qml/GraphEditor/Node.qml
@@ -270,8 +270,6 @@ Item {
height: childrenRect.height
anchors.horizontalCenter: parent.horizontalCenter
- enabled: !root.isCompatibilityNode
-
Column {
id: attributesColumn
width: parent.width
@@ -311,6 +309,7 @@ Item {
id: inputs
width: parent.width
spacing: 3
+
Repeater {
model: node ? node.attributes : undefined
delegate: Loader {
@@ -326,7 +325,7 @@ Item {
property real globalX: root.x + nodeAttributes.x + inputs.x + inputLoader.x + inPin.x
property real globalY: root.y + nodeAttributes.y + inputs.y + inputLoader.y + inPin.y
- readOnly: root.readOnly
+ readOnly: root.readOnly || object.isReadOnly
Component.onCompleted: attributePinCreated(attribute, inPin)
Component.onDestruction: attributePinDeleted(attribute, inPin)
onPressed: root.pressed(mouse)
@@ -387,7 +386,7 @@ Item {
Behavior on height { PropertyAnimation {easing.type: Easing.Linear} }
visible: (height == childrenRect.height)
attribute: object
- readOnly: root.readOnly
+ readOnly: root.readOnly || object.isReadOnly
Component.onCompleted: attributePinCreated(attribute, inPin)
Component.onDestruction: attributePinDeleted(attribute, inPin)
onPressed: root.pressed(mouse)
diff --git a/meshroom/ui/qml/GraphEditor/NodeEditor.qml b/meshroom/ui/qml/GraphEditor/NodeEditor.qml
index ba3e33bf61..42c361db8a 100644
--- a/meshroom/ui/qml/GraphEditor/NodeEditor.qml
+++ b/meshroom/ui/qml/GraphEditor/NodeEditor.qml
@@ -1,5 +1,6 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
+import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import Controls 1.0
@@ -19,15 +20,6 @@ Panel {
signal attributeDoubleClicked(var mouse, var attribute)
signal upgradeRequest()
- Item {
- id: m
- property int chunkCurrentIndex: 0
- }
-
- onNodeChanged: {
- m.chunkCurrentIndex = 0 // Needed to avoid invalid state of ChunksListView
- }
-
title: "Node" + (node !== null ? " - " + node.label + "" : "")
icon: MaterialLabel { text: MaterialIcons.tune }
@@ -114,7 +106,16 @@ Panel {
Component {
id: editor_component
- ColumnLayout {
+ Controls1.SplitView {
+ anchors.fill: parent
+
+ // The list of chunks
+ ChunksListView {
+ id: chunksLV
+ visible: (tabBar.currentIndex >= 1 && tabBar.currentIndex <= 3)
+ chunks: root.node.chunks
+ }
+
StackLayout {
Layout.fillHeight: true
Layout.fillWidth: true
@@ -122,35 +123,65 @@ Panel {
currentIndex: tabBar.currentIndex
AttributeEditor {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
model: root.node.attributes
readOnly: root.readOnly || root.isCompatibilityNode
onAttributeDoubleClicked: root.attributeDoubleClicked(mouse, attribute)
onUpgradeRequest: root.upgradeRequest()
}
- NodeLog {
- id: nodeLog
- node: root.node
- chunkCurrentIndex: m.chunkCurrentIndex
- onChangeCurrentChunk: { m.chunkCurrentIndex = chunkIndex }
+ Loader {
+ active: (tabBar.currentIndex === 1)
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ sourceComponent: NodeLog {
+ // anchors.fill: parent
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ width: parent.width
+ height: parent.height
+ id: nodeLog
+ node: root.node
+ currentChunkIndex: chunksLV.currentIndex
+ currentChunk: chunksLV.currentChunk
+ }
}
- NodeStatistics {
- id: nodeStatistics
- node: root.node
- chunkCurrentIndex: m.chunkCurrentIndex
- onChangeCurrentChunk: { m.chunkCurrentIndex = chunkIndex }
+ Loader {
+ active: (tabBar.currentIndex === 2)
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ sourceComponent: NodeStatistics {
+ id: nodeStatistics
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ node: root.node
+ currentChunkIndex: chunksLV.currentIndex
+ currentChunk: chunksLV.currentChunk
+ }
}
- NodeStatus {
- id: nodeStatus
- node: root.node
- chunkCurrentIndex: m.chunkCurrentIndex
- onChangeCurrentChunk: { m.chunkCurrentIndex = chunkIndex }
+ Loader {
+ active: (tabBar.currentIndex === 3)
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ sourceComponent: NodeStatus {
+ id: nodeStatus
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ node: root.node
+ currentChunkIndex: chunksLV.currentIndex
+ currentChunk: chunksLV.currentChunk
+ }
}
NodeDocumentation {
id: nodeDocumentation
+
+ Layout.fillHeight: true
Layout.fillWidth: true
node: root.node
}
diff --git a/meshroom/ui/qml/GraphEditor/NodeLog.qml b/meshroom/ui/qml/GraphEditor/NodeLog.qml
index bea42622aa..941d5099f9 100644
--- a/meshroom/ui/qml/GraphEditor/NodeLog.qml
+++ b/meshroom/ui/qml/GraphEditor/NodeLog.qml
@@ -1,6 +1,5 @@
import QtQuick 2.11
import QtQuick.Controls 2.3
-import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import Controls 1.0
@@ -16,53 +15,34 @@ import "common.js" as Common
FocusScope {
id: root
property variant node
- property alias chunkCurrentIndex: chunksLV.currentIndex
- signal changeCurrentChunk(int chunkIndex)
+ property int currentChunkIndex
+ property variant currentChunk
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
SystemPalette { id: activePalette }
- Controls1.SplitView {
+ Loader {
+ id: componentLoader
+ clip: true
anchors.fill: parent
- // The list of chunks
- ChunksListView {
- id: chunksLV
- Layout.fillHeight: true
- model: node.chunks
- onChangeCurrentChunk: root.changeCurrentChunk(chunkIndex)
- }
-
- Loader {
- id: componentLoader
- clip: true
- Layout.fillWidth: true
- Layout.fillHeight: true
- property url source
-
- property string currentFile: chunksLV.currentChunk ? chunksLV.currentChunk["logFile"] : ""
- onCurrentFileChanged: {
- // only set text file viewer source when ListView is fully ready
- // (either empty or fully populated with a valid currentChunk)
- // to avoid going through an empty url when switching between two nodes
+ property string currentFile: (root.currentChunkIndex >= 0 && root.currentChunk) ? root.currentChunk["logFile"] : ""
+ property url source: Filepath.stringToUrl(currentFile)
- if(!chunksLV.count || chunksLV.currentChunk)
- componentLoader.source = Filepath.stringToUrl(currentFile);
-
- }
+ sourceComponent: textFileViewerComponent
+ }
- sourceComponent: textFileViewerComponent
- }
+ Component {
+ id: textFileViewerComponent
- Component {
- id: textFileViewerComponent
- TextFileViewer {
- id: textFileViewer
- source: componentLoader.source
- Layout.fillWidth: true
- Layout.fillHeight: true
- autoReload: chunksLV.currentChunk !== undefined && chunksLV.currentChunk.statusName === "RUNNING"
- // source is set in fileSelector
- }
+ TextFileViewer {
+ id: textFileViewer
+ anchors.fill: parent
+ source: componentLoader.source
+ autoReload: root.currentChunk !== undefined && root.currentChunk.statusName === "RUNNING"
+ // source is set in fileSelector
}
}
}
diff --git a/meshroom/ui/qml/GraphEditor/NodeStatistics.qml b/meshroom/ui/qml/GraphEditor/NodeStatistics.qml
index 5e4ec3e80a..ee943693a3 100644
--- a/meshroom/ui/qml/GraphEditor/NodeStatistics.qml
+++ b/meshroom/ui/qml/GraphEditor/NodeStatistics.qml
@@ -4,6 +4,7 @@ import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import Controls 1.0
+import Utils 1.0
import "common.js" as Common
@@ -15,50 +16,46 @@ import "common.js" as Common
*/
FocusScope {
id: root
+
property variant node
- property alias chunkCurrentIndex: chunksLV.currentIndex
- signal changeCurrentChunk(int chunkIndex)
+ property variant currentChunkIndex
+ property variant currentChunk
SystemPalette { id: activePalette }
- Controls1.SplitView {
+ Loader {
+ id: componentLoader
+ clip: true
anchors.fill: parent
+ property string currentFile: currentChunk ? currentChunk["statisticsFile"] : ""
+ property url source: Filepath.stringToUrl(currentFile)
- // The list of chunks
- ChunksListView {
- id: chunksLV
- Layout.fillHeight: true
- model: node.chunks
- onChangeCurrentChunk: root.changeCurrentChunk(chunkIndex)
- }
+ sourceComponent: chunksLV.chunksSummary ? statViewerComponent : chunkStatViewerComponent
+ }
- Loader {
- id: componentLoader
- clip: true
- Layout.fillWidth: true
- Layout.fillHeight: true
- property url source
+ Component {
+ id: chunkStatViewerComponent
+ StatViewer {
+ id: statViewer
+ anchors.fill: parent
+ source: componentLoader.source
+ }
+ }
- property string currentFile: chunksLV.currentChunk ? chunksLV.currentChunk["statisticsFile"] : ""
- onCurrentFileChanged: {
- // only set text file viewer source when ListView is fully ready
- // (either empty or fully populated with a valid currentChunk)
- // to avoid going through an empty url when switching between two nodes
+ Component {
+ id: statViewerComponent
- if(!chunksLV.count || chunksLV.currentChunk)
- componentLoader.source = Filepath.stringToUrl(currentFile);
+ Column {
+ spacing: 2
+ KeyValue {
+ key: "Time"
+ property real time: node.elapsedTime
+ value: time > 0.0 ? Format.sec2time(time) : "-"
}
-
- sourceComponent: statViewerComponent
- }
-
- Component {
- id: statViewerComponent
- StatViewer {
- id: statViewer
- Layout.fillWidth: true
- Layout.fillHeight: true
- source: componentLoader.source
+ KeyValue {
+ key: "Cumulated Time"
+ property real time: node.recursiveElapsedTime
+ value: time > 0.0 ? Format.sec2time(time) : "-"
}
}
}
diff --git a/meshroom/ui/qml/GraphEditor/NodeStatus.qml b/meshroom/ui/qml/GraphEditor/NodeStatus.qml
index 063bcae583..8de87fbc3e 100644
--- a/meshroom/ui/qml/GraphEditor/NodeStatus.qml
+++ b/meshroom/ui/qml/GraphEditor/NodeStatus.qml
@@ -1,6 +1,5 @@
import QtQuick 2.11
import QtQuick.Controls 2.3
-import QtQuick.Controls 1.4 as Controls1 // SplitView
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import Controls 1.0
@@ -16,169 +15,148 @@ import "common.js" as Common
FocusScope {
id: root
property variant node
- property alias chunkCurrentIndex: chunksLV.currentIndex
- signal changeCurrentChunk(int chunkIndex)
+ property variant currentChunkIndex
+ property variant currentChunk
SystemPalette { id: activePalette }
- Controls1.SplitView {
+ Loader {
+ id: componentLoader
+ clip: true
anchors.fill: parent
- // The list of chunks
- ChunksListView {
- id: chunksLV
- Layout.fillHeight: true
- model: node.chunks
- onChangeCurrentChunk: root.changeCurrentChunk(chunkIndex)
- }
+ property string currentFile: (root.currentChunkIndex >= 0) ? root.currentChunk["statusFile"] : ""
+ property url source: Filepath.stringToUrl(currentFile)
+
+ sourceComponent: statViewerComponent
+ }
- Loader {
- id: componentLoader
- clip: true
- Layout.fillWidth: true
- Layout.fillHeight: true
- property url source
-
- property string currentFile: chunksLV.currentChunk ? chunksLV.currentChunk["statusFile"] : ""
- onCurrentFileChanged: {
- // only set text file viewer source when ListView is fully ready
- // (either empty or fully populated with a valid currentChunk)
- // to avoid going through an empty url when switching between two nodes
-
- if(!chunksLV.count || chunksLV.currentChunk)
- componentLoader.source = Filepath.stringToUrl(currentFile);
+ Component {
+ id: statViewerComponent
+ Item {
+ id: statusViewer
+ property url source: componentLoader.source
+ property var lastModified: undefined
+
+ onSourceChanged: {
+ statusListModel.readSourceFile()
}
- sourceComponent: statViewerComponent
- }
+ ListModel {
+ id: statusListModel
- Component {
- id: statViewerComponent
- Item {
- id: statusViewer
- property url source: componentLoader.source
- property var lastModified: undefined
+ function readSourceFile() {
+ // make sure we are trying to load a statistics file
+ if(!Filepath.urlToString(source).endsWith("status"))
+ return;
- onSourceChanged: {
- statusListModel.readSourceFile()
- }
+ var xhr = new XMLHttpRequest;
+ xhr.open("GET", source);
- ListModel {
- id: statusListModel
-
- function readSourceFile() {
- // make sure we are trying to load a statistics file
- if(!Filepath.urlToString(source).endsWith("status"))
- return;
-
- var xhr = new XMLHttpRequest;
- xhr.open("GET", source);
-
- xhr.onreadystatechange = function() {
- if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
- // console.warn("StatusListModel: read valid file")
- if(lastModified === undefined || lastModified !== xhr.getResponseHeader('Last-Modified')) {
- lastModified = xhr.getResponseHeader('Last-Modified')
- try {
- var jsonObject = JSON.parse(xhr.responseText);
-
- var entries = [];
- // prepare data to populate the ListModel from the input json object
- for(var key in jsonObject)
- {
- var entry = {};
- entry["key"] = key;
- entry["value"] = String(jsonObject[key]);
- entries.push(entry);
- }
- // reset the model with prepared data (limit to one update event)
- statusListModel.clear();
- statusListModel.append(entries);
- }
- catch(exc)
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
+ // console.warn("StatusListModel: read valid file")
+ if(lastModified === undefined || lastModified !== xhr.getResponseHeader('Last-Modified')) {
+ lastModified = xhr.getResponseHeader('Last-Modified')
+ try {
+ var jsonObject = JSON.parse(xhr.responseText);
+
+ var entries = [];
+ // prepare data to populate the ListModel from the input json object
+ for(var key in jsonObject)
{
- // console.warn("StatusListModel: failed to read file")
- lastModified = undefined;
- statusListModel.clear();
+ var entry = {};
+ entry["key"] = key;
+ entry["value"] = String(jsonObject[key]);
+ entries.push(entry);
}
+ // reset the model with prepared data (limit to one update event)
+ statusListModel.clear();
+ statusListModel.append(entries);
+ }
+ catch(exc)
+ {
+ // console.warn("StatusListModel: failed to read file")
+ lastModified = undefined;
+ statusListModel.clear();
}
}
- else
- {
- // console.warn("StatusListModel: invalid file")
- lastModified = undefined;
- statusListModel.clear();
- }
- };
- xhr.send();
- }
+ }
+ else
+ {
+ // console.warn("StatusListModel: invalid file")
+ lastModified = undefined;
+ statusListModel.clear();
+ }
+ };
+ xhr.send();
}
+ }
- ListView {
- id: statusListView
- anchors.fill: parent
- spacing: 3
- model: statusListModel
-
- delegate: Rectangle {
- color: activePalette.window
+ ListView {
+ id: statusListView
+ anchors.fill: parent
+ spacing: 3
+ model: statusListModel
+
+ delegate: Rectangle {
+ color: activePalette.window
+ width: parent.width
+ height: childrenRect.height
+ RowLayout {
width: parent.width
- height: childrenRect.height
- RowLayout {
- width: parent.width
- Rectangle {
- id: statusKey
- anchors.margins: 2
- // height: statusValue.height
- color: Qt.darker(activePalette.window, 1.1)
- Layout.preferredWidth: sizeHandle.x
- Layout.minimumWidth: 10.0 * Qt.application.font.pixelSize
- Layout.maximumWidth: 15.0 * Qt.application.font.pixelSize
- Layout.fillWidth: false
- Layout.fillHeight: true
- Label {
- text: key
- anchors.fill: parent
- anchors.top: parent.top
- topPadding: 4
- leftPadding: 6
- verticalAlignment: TextEdit.AlignTop
- elide: Text.ElideRight
- }
- }
- TextArea {
- id: statusValue
- text: value
- anchors.margins: 2
- Layout.fillWidth: true
- wrapMode: Label.WrapAtWordBoundaryOrAnywhere
- textFormat: TextEdit.PlainText
-
- readOnly: true
- selectByMouse: true
- background: Rectangle { anchors.fill: parent; color: Qt.darker(activePalette.window, 1.05) }
+ Rectangle {
+ id: statusKey
+ anchors.margins: 2
+ // height: statusValue.height
+ color: Qt.darker(activePalette.window, 1.1)
+ Layout.preferredWidth: sizeHandle.x
+ Layout.minimumWidth: 10.0 * Qt.application.font.pixelSize
+ Layout.maximumWidth: 15.0 * Qt.application.font.pixelSize
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Label {
+ text: key
+ anchors.fill: parent
+ anchors.top: parent.top
+ topPadding: 4
+ leftPadding: 6
+ verticalAlignment: TextEdit.AlignTop
+ elide: Text.ElideRight
}
}
+ TextArea {
+ id: statusValue
+ text: value
+ anchors.margins: 2
+ Layout.fillWidth: true
+ wrapMode: Label.WrapAtWordBoundaryOrAnywhere
+ textFormat: TextEdit.PlainText
+
+ readOnly: true
+ selectByMouse: true
+ background: Rectangle { anchors.fill: parent; color: Qt.darker(activePalette.window, 1.05) }
+ }
}
}
+ }
- // Categories resize handle
- Rectangle {
- id: sizeHandle
- height: parent.contentHeight
- width: 1
- x: parent.width * 0.2
- MouseArea {
- anchors.fill: parent
- anchors.margins: -4
- cursorShape: Qt.SizeHorCursor
- drag {
- target: parent
- axis: Drag.XAxis
- threshold: 0
- minimumX: statusListView.width * 0.2
- maximumX: statusListView.width * 0.8
- }
+ // Categories resize handle
+ Rectangle {
+ id: sizeHandle
+ height: parent.contentHeight
+ width: 1
+ x: parent.width * 0.2
+ MouseArea {
+ anchors.fill: parent
+ anchors.margins: -4
+ cursorShape: Qt.SizeHorCursor
+ drag {
+ target: parent
+ axis: Drag.XAxis
+ threshold: 0
+ minimumX: statusListView.width * 0.2
+ maximumX: statusListView.width * 0.8
}
}
}
diff --git a/meshroom/ui/qml/GraphEditor/StatViewer.qml b/meshroom/ui/qml/GraphEditor/StatViewer.qml
index 3923ef4013..8db23ddce0 100644
--- a/meshroom/ui/qml/GraphEditor/StatViewer.qml
+++ b/meshroom/ui/qml/GraphEditor/StatViewer.qml
@@ -257,12 +257,14 @@ Item {
var gpuUsedMemorySerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "Memory", valueGpuX, valueGpuY)
var gpuTemperatureSerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "Temperature", valueGpuX, valueGpuY)
+ var gpuMemoryRatio = root.gpuTotalMemory > 0 ? (100 / root.gpuTotalMemory) : 1;
+
if(gpuUsedMemory.length === 1) {
gpuUsedSerie.append(0, gpuUsed[0])
gpuUsedSerie.append(1 * root.deltaTime, gpuUsed[0])
- gpuUsedMemorySerie.append(0, gpuUsedMemory[0] / root.gpuTotalMemory * 100)
- gpuUsedMemorySerie.append(1 * root.deltaTime, gpuUsedMemory[0] / root.gpuTotalMemory * 100)
+ gpuUsedMemorySerie.append(0, gpuUsedMemory[0] * gpuMemoryRatio)
+ gpuUsedMemorySerie.append(1 * root.deltaTime, gpuUsedMemory[0] * gpuMemoryRatio)
gpuTemperatureSerie.append(0, gpuTemperature[0])
gpuTemperatureSerie.append(1 * root.deltaTime, gpuTemperature[0])
@@ -271,7 +273,7 @@ Item {
for(var i = 0; i < gpuUsedMemory.length; i++) {
gpuUsedSerie.append(i * root.deltaTime, gpuUsed[i])
- gpuUsedMemorySerie.append(i * root.deltaTime, gpuUsedMemory[i] / root.gpuTotalMemory * 100)
+ gpuUsedMemorySerie.append(i * root.deltaTime, gpuUsedMemory[i] * gpuMemoryRatio)
gpuTemperatureSerie.append(i * root.deltaTime, gpuTemperature[i])
root.gpuMaxAxis = Math.max(gpuMaxAxis, gpuTemperature[i])
diff --git a/meshroom/ui/qml/Utils/format.js b/meshroom/ui/qml/Utils/format.js
index 72b732e08d..c439c0b1d4 100644
--- a/meshroom/ui/qml/Utils/format.js
+++ b/meshroom/ui/qml/Utils/format.js
@@ -13,3 +13,12 @@ function plainToHtml(t) {
var escaped = t.replace(/&/g, '&').replace(//g, '>'); // escape text
return escaped.replace(/\n/g, '
'); // replace line breaks
}
+
+function sec2time(time) {
+ var pad = function(num, size) { return ('000' + num).slice(size * -1); },
+ hours = Math.floor(time / 60 / 60),
+ minutes = Math.floor(time / 60) % 60,
+ seconds = Math.floor(time - minutes * 60);
+
+ return pad(hours, 2) + ':' + pad(minutes, 2) + ':' + pad(seconds, 2)
+}
diff --git a/meshroom/ui/qml/Viewer3D/AlembicLoader.qml b/meshroom/ui/qml/Viewer3D/AlembicLoader.qml
index 641b3c2e2b..9b8bbf5f33 100644
--- a/meshroom/ui/qml/Viewer3D/AlembicLoader.qml
+++ b/meshroom/ui/qml/Viewer3D/AlembicLoader.qml
@@ -46,13 +46,25 @@ AlembicEntity {
// Qt 5.13: binding cameraPicker.enabled to cameraPickerEnabled
// causes rendering issues when entity gets disabled.
// set CuboidMesh extent to 0 to disable picking.
+ property color customColor: Qt.hsva((parseInt(viewId) / 255.0) % 1.0, 0.3, 1.0, 1.0)
property real extent: cameraPickingEnabled ? 0.2 : 0
components: [
- CuboidMesh { xExtent: parent.extent; yExtent: xExtent; zExtent: xExtent },
+ // Use cuboid to represent the camera
+ Transform {
+ translation: Qt.vector3d(0, 0, 0.5 * cameraBack.zExtent)
+ },
+ CuboidMesh { id: cameraBack; xExtent: parent.extent; yExtent: xExtent; zExtent: xExtent * 0.2 },
+ /*
+ // Use a stick to represent the camera
+ Transform {
+ translation: Qt.vector3d(0, 0, 0.5 * cameraStick.zExtent)
+ },
+ CuboidMesh { id: cameraStick; xExtent: parent.extent * 0.2; yExtent: xExtent; zExtent: xExtent * 50.0 },
+ */
PhongMaterial{
id: mat
- ambient: viewId === _reconstruction.selectedViewId ? activePalette.highlight : "#CCC"
+ ambient: viewId === _reconstruction.selectedViewId ? activePalette.highlight : customColor // "#CCC"
diffuse: cameraPicker.containsMouse ? Qt.lighter(activePalette.highlight, 1.2) : ambient
},
ObjectPicker {
diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py
index 54e99198bc..c37ffafc67 100755
--- a/meshroom/ui/reconstruction.py
+++ b/meshroom/ui/reconstruction.py
@@ -570,7 +570,7 @@ def getViewpoints(self):
return self._cameraInit.viewpoints.value if self._cameraInit else QObjectListModel(parent=self)
def updateCameraInits(self):
- cameraInits = self._graph.nodesByType("CameraInit", sortedByIndex=True)
+ cameraInits = self._graph.nodesOfType("CameraInit", sortedByIndex=True)
if set(self._cameraInits.objectList()) == set(cameraInits):
return
self._cameraInits.setObjectList(cameraInits)
@@ -739,9 +739,10 @@ def handleFilesDrop(self, drop, cameraInit):
"",
))
else:
- panoramaInitNodes = self.graph.nodesByType('PanoramaInit')
+ panoramaInitNodes = self.graph.nodesOfType('PanoramaInit')
for panoramaInfoFile in filesByType.panoramaInfo:
for panoramaInitNode in panoramaInitNodes:
+ panoramaInitNode.attribute('initializeCameras').value = 'File'
panoramaInitNode.attribute('config').value = panoramaInfoFile
if panoramaInitNodes:
self.info.emit(
diff --git a/tests/test_graph.py b/tests/test_graph.py
index ed92447524..87d52c467a 100644
--- a/tests/test_graph.py
+++ b/tests/test_graph.py
@@ -237,7 +237,7 @@ def test_graph_nodes_sorting():
ls1 = graph.addNewNode('Ls')
ls2 = graph.addNewNode('Ls')
- assert graph.nodesByType('Ls', sortedByIndex=True) == [ls0, ls1, ls2]
+ assert graph.nodesOfType('Ls', sortedByIndex=True) == [ls0, ls1, ls2]
graph = Graph('')
# 'Random' creation order (what happens when loading a file)
@@ -245,7 +245,7 @@ def test_graph_nodes_sorting():
ls0 = graph.addNewNode('Ls', name='Ls_0')
ls1 = graph.addNewNode('Ls', name='Ls_1')
- assert graph.nodesByType('Ls', sortedByIndex=True) == [ls0, ls1, ls2]
+ assert graph.nodesOfType('Ls', sortedByIndex=True) == [ls0, ls1, ls2]
def test_duplicate_nodes():