Merge pull request #1182 from alicevision/dev/graphDeps

Graph Dependencies
This commit is contained in:
Fabien Castan 2021-01-04 14:30:44 +01:00 committed by GitHub
commit c502ee1e73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 448 additions and 254 deletions

View file

@ -93,6 +93,9 @@ class Attribute(BaseObject):
def getType(self): def getType(self):
return self.attributeDesc.__class__.__name__ return self.attributeDesc.__class__.__name__
def getBaseType(self):
return self.getType()
def getLabel(self): def getLabel(self):
return self._label return self._label
@ -137,7 +140,7 @@ class Attribute(BaseObject):
self.valueChanged.emit() self.valueChanged.emit()
def resetValue(self): def resetValue(self):
self._value = "" self._value = self.attributeDesc.value
def requestGraphUpdate(self): def requestGraphUpdate(self):
if self.node.graph: if self.node.graph:
@ -258,6 +261,7 @@ class Attribute(BaseObject):
fullName = Property(str, getFullName, constant=True) fullName = Property(str, getFullName, constant=True)
label = Property(str, getLabel, constant=True) label = Property(str, getLabel, constant=True)
type = Property(str, getType, constant=True) type = Property(str, getType, constant=True)
baseType = Property(str, getType, constant=True)
desc = Property(desc.Attribute, lambda self: self.attributeDesc, constant=True) desc = Property(desc.Attribute, lambda self: self.attributeDesc, constant=True)
valueChanged = Signal() valueChanged = Signal()
value = Property(Variant, _get_value, _set_value, notify=valueChanged) value = Property(Variant, _get_value, _set_value, notify=valueChanged)
@ -292,6 +296,9 @@ class ListAttribute(Attribute):
def __len__(self): def __len__(self):
return len(self._value) return len(self._value)
def getBaseType(self):
return self.attributeDesc.elementDesc.__class__.__name__
def at(self, idx): def at(self, idx):
""" Returns child attribute at index 'idx' """ """ Returns child attribute at index 'idx' """
# implement 'at' rather than '__getitem__' # implement 'at' rather than '__getitem__'
@ -396,6 +403,7 @@ class ListAttribute(Attribute):
# Override value property setter # Override value property setter
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged) value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
isDefault = Property(bool, _isDefault, notify=Attribute.valueChanged) isDefault = Property(bool, _isDefault, notify=Attribute.valueChanged)
baseType = Property(str, getBaseType, constant=True)
class GroupAttribute(Attribute): class GroupAttribute(Attribute):

View file

@ -85,9 +85,10 @@ class Visitor(object):
Base class for Graph Visitors that does nothing. Base class for Graph Visitors that does nothing.
Sub-classes can override any method to implement specific algorithms. Sub-classes can override any method to implement specific algorithms.
""" """
def __init__(self, reverse): def __init__(self, reverse, dependenciesOnly):
super(Visitor, self).__init__() super(Visitor, self).__init__()
self.reverse = reverse self.reverse = reverse
self.dependenciesOnly = dependenciesOnly
# def initializeVertex(self, s, g): # def initializeVertex(self, s, g):
# '''is invoked on every vertex of the graph before the start of the graph search.''' # '''is invoked on every vertex of the graph before the start of the graph search.'''
@ -383,7 +384,7 @@ class Graph(BaseObject):
Returns: Returns:
OrderedDict[Node, Node]: the source->duplicate map OrderedDict[Node, Node]: the source->duplicate map
""" """
srcNodes, srcEdges = self.dfsOnDiscover(startNodes=[fromNode], reverse=True) srcNodes, srcEdges = self.dfsOnDiscover(startNodes=[fromNode], reverse=True, dependenciesOnly=True)
# use OrderedDict to keep duplicated nodes creation order # use OrderedDict to keep duplicated nodes creation order
duplicates = OrderedDict() duplicates = OrderedDict()
@ -581,13 +582,13 @@ class Graph(BaseObject):
def edge(self, dstAttributeName): def edge(self, dstAttributeName):
return self._edges.get(dstAttributeName) return self._edges.get(dstAttributeName)
def getLeafNodes(self): def getLeafNodes(self, dependenciesOnly):
nodesWithOutput = set([edge.src.node for edge in self.edges]) nodesWithOutputLink = set([edge.src.node for edge in self.getEdges(dependenciesOnly)])
return set(self._nodes) - nodesWithOutput return set(self._nodes) - nodesWithOutputLink
def getRootNodes(self): def getRootNodes(self, dependenciesOnly):
nodesWithInput = set([edge.dst.node for edge in self.edges]) nodesWithInputLink = set([edge.dst.node for edge in self.getEdges(dependenciesOnly)])
return set(self._nodes) - nodesWithInput return set(self._nodes) - nodesWithInputLink
@changeTopology @changeTopology
def addEdge(self, srcAttr, dstAttr): def addEdge(self, srcAttr, dstAttr):
@ -635,21 +636,21 @@ class Graph(BaseObject):
minDepth, maxDepth = self._nodesMinMaxDepths[node] minDepth, maxDepth = self._nodesMinMaxDepths[node]
return minDepth if minimal else maxDepth return minDepth if minimal else maxDepth
def getInputEdges(self, node): def getInputEdges(self, node, dependenciesOnly):
return set([edge for edge in self.edges if edge.dst.node is node]) return set([edge for edge in self.getEdges(dependenciesOnly=dependenciesOnly) if edge.dst.node is node])
def _getInputEdgesPerNode(self): def _getInputEdgesPerNode(self, dependenciesOnly):
nodeEdges = defaultdict(set) nodeEdges = defaultdict(set)
for edge in self.edges: for edge in self.getEdges(dependenciesOnly=dependenciesOnly):
nodeEdges[edge.dst.node].add(edge.src.node) nodeEdges[edge.dst.node].add(edge.src.node)
return nodeEdges return nodeEdges
def _getOutputEdgesPerNode(self): def _getOutputEdgesPerNode(self, dependenciesOnly):
nodeEdges = defaultdict(set) nodeEdges = defaultdict(set)
for edge in self.edges: for edge in self.getEdges(dependenciesOnly=dependenciesOnly):
nodeEdges[edge.src.node].add(edge.dst.node) nodeEdges[edge.src.node].add(edge.dst.node)
return nodeEdges return nodeEdges
@ -657,7 +658,7 @@ class Graph(BaseObject):
def dfs(self, visitor, startNodes=None, longestPathFirst=False): def dfs(self, visitor, startNodes=None, longestPathFirst=False):
# Default direction (visitor.reverse=False): from node to root # Default direction (visitor.reverse=False): from node to root
# Reverse direction (visitor.reverse=True): from node to leaves # Reverse direction (visitor.reverse=True): from node to leaves
nodeChildren = self._getOutputEdgesPerNode() if visitor.reverse else self._getInputEdgesPerNode() nodeChildren = self._getOutputEdgesPerNode(visitor.dependenciesOnly) if visitor.reverse else self._getInputEdgesPerNode(visitor.dependenciesOnly)
# Initialize color map # Initialize color map
colors = {} colors = {}
for u in self._nodes: for u in self._nodes:
@ -668,7 +669,7 @@ class Graph(BaseObject):
# it is not possible to handle this case at the moment # it is not possible to handle this case at the moment
raise NotImplementedError("Graph.dfs(): longestPathFirst=True and visitor.reverse=True are not compatible yet.") raise NotImplementedError("Graph.dfs(): longestPathFirst=True and visitor.reverse=True are not compatible yet.")
nodes = startNodes or (self.getRootNodes() if visitor.reverse else self.getLeafNodes()) nodes = startNodes or (self.getRootNodes(visitor.dependenciesOnly) if visitor.reverse else self.getLeafNodes(visitor.dependenciesOnly))
if longestPathFirst: if longestPathFirst:
# Graph topology must be known and node depths up-to-date # Graph topology must be known and node depths up-to-date
@ -711,7 +712,7 @@ class Graph(BaseObject):
colors[u] = BLACK colors[u] = BLACK
visitor.finishVertex(u, self) visitor.finishVertex(u, self)
def dfsOnFinish(self, startNodes=None, longestPathFirst=False, reverse=False): def dfsOnFinish(self, startNodes=None, longestPathFirst=False, reverse=False, dependenciesOnly=False):
""" """
Return the node chain from startNodes to the graph roots/leaves. Return the node chain from startNodes to the graph roots/leaves.
Order is defined by the visit and finishVertex event. Order is defined by the visit and finishVertex event.
@ -728,13 +729,13 @@ class Graph(BaseObject):
""" """
nodes = [] nodes = []
edges = [] edges = []
visitor = Visitor(reverse=reverse) visitor = Visitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
visitor.finishVertex = lambda vertex, graph: nodes.append(vertex) visitor.finishVertex = lambda vertex, graph: nodes.append(vertex)
visitor.finishEdge = lambda edge, graph: edges.append(edge) visitor.finishEdge = lambda edge, graph: edges.append(edge)
self.dfs(visitor=visitor, startNodes=startNodes, longestPathFirst=longestPathFirst) self.dfs(visitor=visitor, startNodes=startNodes, longestPathFirst=longestPathFirst)
return nodes, edges return nodes, edges
def dfsOnDiscover(self, startNodes=None, filterTypes=None, longestPathFirst=False, reverse=False): def dfsOnDiscover(self, startNodes=None, filterTypes=None, longestPathFirst=False, reverse=False, dependenciesOnly=False):
""" """
Return the node chain from startNodes to the graph roots/leaves. Return the node chain from startNodes to the graph roots/leaves.
Order is defined by the visit and discoverVertex event. Order is defined by the visit and discoverVertex event.
@ -753,7 +754,7 @@ class Graph(BaseObject):
""" """
nodes = [] nodes = []
edges = [] edges = []
visitor = Visitor(reverse=reverse) visitor = Visitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
def discoverVertex(vertex, graph): def discoverVertex(vertex, graph):
if not filterTypes or vertex.nodeType in filterTypes: if not filterTypes or vertex.nodeType in filterTypes:
@ -777,7 +778,7 @@ class Graph(BaseObject):
""" """
nodes = [] nodes = []
edges = [] edges = []
visitor = Visitor(reverse=False) visitor = Visitor(reverse=False, dependenciesOnly=True)
def discoverVertex(vertex, graph): def discoverVertex(vertex, graph):
if vertex.hasStatus(Status.SUCCESS): if vertex.hasStatus(Status.SUCCESS):
@ -832,7 +833,7 @@ class Graph(BaseObject):
self._computationBlocked.clear() self._computationBlocked.clear()
compatNodes = [] compatNodes = []
visitor = Visitor(reverse=False) visitor = Visitor(reverse=False, dependenciesOnly=True)
def discoverVertex(vertex, graph): def discoverVertex(vertex, graph):
# initialize depths # initialize depths
@ -866,7 +867,7 @@ class Graph(BaseObject):
# propagate inputVertex computability # propagate inputVertex computability
self._computationBlocked[currentVertex] |= self._computationBlocked[inputVertex] self._computationBlocked[currentVertex] |= self._computationBlocked[inputVertex]
leaves = self.getLeafNodes() leaves = self.getLeafNodes(visitor.dependenciesOnly)
visitor.finishEdge = finishEdge visitor.finishEdge = finishEdge
visitor.discoverVertex = discoverVertex visitor.discoverVertex = discoverVertex
self.dfs(visitor=visitor, startNodes=leaves) self.dfs(visitor=visitor, startNodes=leaves)
@ -890,7 +891,7 @@ class Graph(BaseObject):
""" """
nodesStack = [] nodesStack = []
edgesScore = defaultdict(lambda: 0) edgesScore = defaultdict(lambda: 0)
visitor = Visitor(reverse=False) visitor = Visitor(reverse=False, dependenciesOnly=False)
def finishEdge(edge, graph): def finishEdge(edge, graph):
u, v = edge u, v = edge
@ -926,18 +927,34 @@ class Graph(BaseObject):
flowEdges.append(link) flowEdges.append(link)
return flowEdges return flowEdges
def getInputNodes(self, node, recursive=False): def getEdges(self, dependenciesOnly=False):
if not dependenciesOnly:
return self.edges
outEdges = []
for e in self.edges:
attr = e.src
if dependenciesOnly:
if attr.isLink:
attr = attr.getLinkParam(recursive=True)
if not attr.isOutput:
continue
newE = Edge(attr, e.dst)
outEdges.append(newE)
return outEdges
def getInputNodes(self, node, recursive, dependenciesOnly):
""" Return either the first level input nodes of a node or the whole chain. """ """ Return either the first level input nodes of a node or the whole chain. """
if not recursive: if not recursive:
return set([edge.src.node for edge in self.edges if edge.dst.node is node]) return set([edge.src.node for edge in self.getEdges(dependenciesOnly) if edge.dst.node is node])
inputNodes, edges = self.dfsOnDiscover(startNodes=[node], filterTypes=None, reverse=False) inputNodes, edges = self.dfsOnDiscover(startNodes=[node], filterTypes=None, reverse=False)
return inputNodes[1:] # exclude current node return inputNodes[1:] # exclude current node
def getOutputNodes(self, node, recursive=False): def getOutputNodes(self, node, recursive, dependenciesOnly):
""" Return either the first level output nodes of a node or the whole chain. """ """ Return either the first level output nodes of a node or the whole chain. """
if not recursive: if not recursive:
return set([edge.dst.node for edge in self.edges if edge.src.node is node]) return set([edge.dst.node for edge in self.getEdges(dependenciesOnly) if edge.src.node is node])
outputNodes, edges = self.dfsOnDiscover(startNodes=[node], filterTypes=None, reverse=True) outputNodes, edges = self.dfsOnDiscover(startNodes=[node], filterTypes=None, reverse=True)
return outputNodes[1:] # exclude current node return outputNodes[1:] # exclude current node
@ -957,8 +974,8 @@ class Graph(BaseObject):
return 0 return 0
class SCVisitor(Visitor): class SCVisitor(Visitor):
def __init__(self, reverse): def __init__(self, reverse, dependenciesOnly):
super(SCVisitor, self).__init__(reverse) super(SCVisitor, self).__init__(reverse, dependenciesOnly)
canCompute = True canCompute = True
canSubmit = True canSubmit = True
@ -969,7 +986,7 @@ class Graph(BaseObject):
if vertex.isExtern(): if vertex.isExtern():
self.canCompute = False self.canCompute = False
visitor = SCVisitor(reverse=False) visitor = SCVisitor(reverse=False, dependenciesOnly=True)
self.dfs(visitor=visitor, startNodes=[startNode]) self.dfs(visitor=visitor, startNodes=[startNode])
return visitor.canCompute + (2 * visitor.canSubmit) return visitor.canCompute + (2 * visitor.canSubmit)
@ -1131,7 +1148,7 @@ class Graph(BaseObject):
@Slot(Node) @Slot(Node)
def clearDataFrom(self, startNode): def clearDataFrom(self, startNode):
for node in self.dfsOnDiscover(startNodes=[startNode], reverse=True)[0]: for node in self.dfsOnDiscover(startNodes=[startNode], reverse=True, dependenciesOnly=True)[0]:
node.clearData() node.clearData()
def iterChunksByStatus(self, status): def iterChunksByStatus(self, status):

View file

@ -589,11 +589,11 @@ class BaseNode(BaseObject):
def minDepth(self): def minDepth(self):
return self.graph.getDepth(self, minimal=True) return self.graph.getDepth(self, minimal=True)
def getInputNodes(self, recursive=False): def getInputNodes(self, recursive, dependenciesOnly):
return self.graph.getInputNodes(self, recursive=recursive) return self.graph.getInputNodes(self, recursive=recursive, dependenciesOnly=dependenciesOnly)
def getOutputNodes(self, recursive=False): def getOutputNodes(self, recursive, dependenciesOnly):
return self.graph.getOutputNodes(self, recursive=recursive) return self.graph.getOutputNodes(self, recursive=recursive, dependenciesOnly=dependenciesOnly)
def toDict(self): def toDict(self):
pass pass
@ -883,7 +883,7 @@ class BaseNode(BaseObject):
# Warning: we must handle some specific cases for global start/stop # Warning: we must handle some specific cases for global start/stop
if self._locked and currentStatus in (Status.ERROR, Status.STOPPED, Status.NONE): if self._locked and currentStatus in (Status.ERROR, Status.STOPPED, Status.NONE):
self.setLocked(False) self.setLocked(False)
inputNodes = self.getInputNodes(recursive=True) inputNodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
for node in inputNodes: for node in inputNodes:
if node.getGlobalStatus() == Status.RUNNING: if node.getGlobalStatus() == Status.RUNNING:
@ -901,8 +901,8 @@ class BaseNode(BaseObject):
if currentStatus == Status.SUCCESS: if currentStatus == Status.SUCCESS:
# At this moment, the node is necessarily locked because of previous if statement # At this moment, the node is necessarily locked because of previous if statement
inputNodes = self.getInputNodes(recursive=True) inputNodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
outputNodes = self.getOutputNodes(recursive=True) outputNodes = self.getOutputNodes(recursive=True, dependenciesOnly=True)
stayLocked = None stayLocked = None
# Check if at least one dependentNode is submitted or currently running # Check if at least one dependentNode is submitted or currently running
@ -918,7 +918,7 @@ class BaseNode(BaseObject):
return return
elif currentStatus in lockedStatus and self._chunks.at(0).statusNodeName == self.name: elif currentStatus in lockedStatus and self._chunks.at(0).statusNodeName == self.name:
self.setLocked(True) self.setLocked(True)
inputNodes = self.getInputNodes(recursive=True) inputNodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
for node in inputNodes: for node in inputNodes:
node.setLocked(True) node.setLocked(True)
return return

View file

@ -147,7 +147,7 @@ class TaskManager(BaseObject):
self.removeNode(node, displayList=False, processList=True) self.removeNode(node, displayList=False, processList=True)
# Remove output nodes from display and computing lists # Remove output nodes from display and computing lists
outputNodes = node.getOutputNodes(recursive=True) outputNodes = node.getOutputNodes(recursive=True, dependenciesOnly=True)
for n in outputNodes: for n in outputNodes:
if n.getGlobalStatus() in (Status.ERROR, Status.SUBMITTED): if n.getGlobalStatus() in (Status.ERROR, Status.SUBMITTED):
n.upgradeStatusTo(Status.NONE) n.upgradeStatusTo(Status.NONE)
@ -184,7 +184,7 @@ class TaskManager(BaseObject):
else: else:
# Check dependencies of toNodes # Check dependencies of toNodes
if not toNodes: if not toNodes:
toNodes = graph.getLeafNodes() toNodes = graph.getLeafNodes(dependenciesOnly=True)
toNodes = list(toNodes) toNodes = list(toNodes)
allReady = self.checkNodesDependencies(graph, toNodes, "COMPUTATION") allReady = self.checkNodesDependencies(graph, toNodes, "COMPUTATION")
@ -402,7 +402,7 @@ class TaskManager(BaseObject):
# Check dependencies of toNodes # Check dependencies of toNodes
if not toNodes: if not toNodes:
toNodes = graph.getLeafNodes() toNodes = graph.getLeafNodes(dependenciesOnly=True)
toNodes = list(toNodes) toNodes = list(toNodes)
allReady = self.checkNodesDependencies(graph, toNodes, "SUBMITTING") allReady = self.checkNodesDependencies(graph, toNodes, "SUBMITTING")

View file

@ -210,10 +210,16 @@ def panoramaHdrPipeline(graph):
ldr2hdrCalibration = graph.addNewNode('LdrToHdrCalibration', ldr2hdrCalibration = graph.addNewNode('LdrToHdrCalibration',
input=ldr2hdrSampling.input, input=ldr2hdrSampling.input,
userNbBrackets=ldr2hdrSampling.userNbBrackets,
byPass=ldr2hdrSampling.byPass,
channelQuantizationPower=ldr2hdrSampling.channelQuantizationPower,
samples=ldr2hdrSampling.output) samples=ldr2hdrSampling.output)
ldr2hdrMerge = graph.addNewNode('LdrToHdrMerge', ldr2hdrMerge = graph.addNewNode('LdrToHdrMerge',
input=ldr2hdrCalibration.input, input=ldr2hdrCalibration.input,
userNbBrackets=ldr2hdrCalibration.userNbBrackets,
byPass=ldr2hdrCalibration.byPass,
channelQuantizationPower=ldr2hdrCalibration.channelQuantizationPower,
response=ldr2hdrCalibration.response) response=ldr2hdrCalibration.response)
featureExtraction = graph.addNewNode('FeatureExtraction', featureExtraction = graph.addNewNode('FeatureExtraction',
@ -233,12 +239,14 @@ def panoramaHdrPipeline(graph):
featureMatching = graph.addNewNode('FeatureMatching', featureMatching = graph.addNewNode('FeatureMatching',
input=imageMatching.input, input=imageMatching.input,
featuresFolders=imageMatching.featuresFolders, featuresFolders=imageMatching.featuresFolders,
imagePairsList=imageMatching.output) imagePairsList=imageMatching.output,
describerTypes=featureExtraction.describerTypes)
panoramaEstimation = graph.addNewNode('PanoramaEstimation', panoramaEstimation = graph.addNewNode('PanoramaEstimation',
input=featureMatching.input, input=featureMatching.input,
featuresFolders=featureMatching.featuresFolders, featuresFolders=featureMatching.featuresFolders,
matchesFolders=[featureMatching.output]) matchesFolders=[featureMatching.output],
describerTypes=featureMatching.describerTypes)
panoramaOrientation = graph.addNewNode('SfMTransform', panoramaOrientation = graph.addNewNode('SfMTransform',
input=panoramaEstimation.output, input=panoramaEstimation.output,
@ -340,11 +348,13 @@ def sfmPipeline(graph):
featureMatching = graph.addNewNode('FeatureMatching', featureMatching = graph.addNewNode('FeatureMatching',
input=imageMatching.input, input=imageMatching.input,
featuresFolders=imageMatching.featuresFolders, featuresFolders=imageMatching.featuresFolders,
imagePairsList=imageMatching.output) imagePairsList=imageMatching.output,
describerTypes=featureExtraction.describerTypes)
structureFromMotion = graph.addNewNode('StructureFromMotion', structureFromMotion = graph.addNewNode('StructureFromMotion',
input=featureMatching.input, input=featureMatching.input,
featuresFolders=featureMatching.featuresFolders, featuresFolders=featureMatching.featuresFolders,
matchesFolders=[featureMatching.output]) matchesFolders=[featureMatching.output],
describerTypes=featureMatching.describerTypes)
return [ return [
cameraInit, cameraInit,
featureExtraction, featureExtraction,
@ -419,16 +429,18 @@ def sfmAugmentation(graph, sourceSfm, withMVS=False):
featureMatching = graph.addNewNode('FeatureMatching', featureMatching = graph.addNewNode('FeatureMatching',
input=imageMatchingMulti.outputCombinedSfM, input=imageMatchingMulti.outputCombinedSfM,
featuresFolders=imageMatchingMulti.featuresFolders, featuresFolders=imageMatchingMulti.featuresFolders,
imagePairsList=imageMatchingMulti.output) imagePairsList=imageMatchingMulti.output,
describerTypes=featureExtraction.describerTypes)
structureFromMotion = graph.addNewNode('StructureFromMotion', structureFromMotion = graph.addNewNode('StructureFromMotion',
input=featureMatching.input, input=featureMatching.input,
featuresFolders=featureMatching.featuresFolders, featuresFolders=featureMatching.featuresFolders,
matchesFolders=[featureMatching.output]) matchesFolders=[featureMatching.output],
describerTypes=featureMatching.describerTypes)
graph.addEdge(sourceSfm.output, imageMatchingMulti.inputB) graph.addEdge(sourceSfm.output, imageMatchingMulti.inputB)
sfmNodes = [ sfmNodes = [
cameraInit, cameraInit,
featureMatching, featureExtraction,
imageMatchingMulti, imageMatchingMulti,
featureMatching, featureMatching,
structureFromMotion structureFromMotion

View file

@ -49,6 +49,23 @@ class LdrToHdrCalibration(desc.CommandLineNode):
value=desc.Node.internalFolder, value=desc.Node.internalFolder,
uid=[0], uid=[0],
), ),
desc.IntParam(
name='userNbBrackets',
label='Number of Brackets',
description='Number of exposure brackets per HDR image (0 for automatic detection).',
value=0,
range=(0, 15, 1),
uid=[],
group='user', # not used directly on the command line
),
desc.IntParam(
name='nbBrackets',
label='Automatic Nb Brackets',
description='Number of exposure brackets used per HDR image. It is detected automatically from input Viewpoints metadata if "userNbBrackets" is 0, else it is equal to "userNbBrackets".',
value=0,
range=(0, 10, 1),
uid=[0],
),
desc.BoolParam( desc.BoolParam(
name='byPass', name='byPass',
label='Bypass', label='Bypass',
@ -87,23 +104,6 @@ class LdrToHdrCalibration(desc.CommandLineNode):
uid=[0], uid=[0],
enabled= lambda node: node.byPass.enabled and not node.byPass.value, enabled= lambda node: node.byPass.enabled and not node.byPass.value,
), ),
desc.IntParam(
name='userNbBrackets',
label='Number of Brackets',
description='Number of exposure brackets per HDR image (0 for automatic detection).',
value=0,
range=(0, 15, 1),
uid=[],
group='user', # not used directly on the command line
),
desc.IntParam(
name='nbBrackets',
label='Automatic Nb Brackets',
description='Number of exposure brackets used per HDR image. It is detected automatically from input Viewpoints metadata if "userNbBrackets" is 0, else it is equal to "userNbBrackets".',
value=0,
range=(0, 10, 1),
uid=[0],
),
desc.IntParam( desc.IntParam(
name='channelQuantizationPower', name='channelQuantizationPower',
label='Channel Quantization Power', label='Channel Quantization Power',

View file

@ -44,7 +44,7 @@ Compute the image warping for each input image in the panorama coordinate system
), ),
desc.IntParam( desc.IntParam(
name='percentUpscale', name='percentUpscale',
label='Upscale ratio', label='Upscale Ratio',
description='Percentage of upscaled pixels.\n' description='Percentage of upscaled pixels.\n'
'\n' '\n'
'How many percent of the pixels will be upscaled (compared to its original resolution):\n' 'How many percent of the pixels will be upscaled (compared to its original resolution):\n'

View file

@ -227,6 +227,9 @@ class AddEdgeCommand(GraphCommand):
self.dstAttr = dst.getFullName() self.dstAttr = dst.getFullName()
self.setText("Connect '{}'->'{}'".format(self.srcAttr, self.dstAttr)) self.setText("Connect '{}'->'{}'".format(self.srcAttr, self.dstAttr))
if src.baseType != dst.baseType:
raise ValueError("Attribute types are not compatible and cannot be connected: '{}'({})->'{}'({})".format(self.srcAttr, src.baseType, self.dstAttr, dst.baseType))
def redoImpl(self): def redoImpl(self):
self.graph.addEdge(self.graph.attribute(self.srcAttr), self.graph.attribute(self.dstAttr)) self.graph.addEdge(self.graph.attribute(self.srcAttr), self.graph.attribute(self.dstAttr))
return True return True

View file

@ -411,7 +411,7 @@ class UIGraph(QObject):
node.clearSubmittedChunks() node.clearSubmittedChunks()
self._taskManager.removeNode(node, displayList=True, processList=True) self._taskManager.removeNode(node, displayList=True, processList=True)
for n in node.getOutputNodes(recursive=True): for n in node.getOutputNodes(recursive=True, dependenciesOnly=True):
n.clearSubmittedChunks() n.clearSubmittedChunks()
self._taskManager.removeNode(n, displayList=True, processList=True) self._taskManager.removeNode(n, displayList=True, processList=True)
@ -524,9 +524,11 @@ class UIGraph(QObject):
startNode (Node): the node to start from. startNode (Node): the node to start from.
""" """
with self.groupedGraphModification("Remove Nodes from {}".format(startNode.name)): with self.groupedGraphModification("Remove Nodes from {}".format(startNode.name)):
nodes, _ = self._graph.dfsOnDiscover(startNodes=[startNode], reverse=True, dependenciesOnly=True)
# Perform nodes removal from leaves to start node so that edges # Perform nodes removal from leaves to start node so that edges
# can be re-created in correct order on redo. # can be re-created in correct order on redo.
[self.removeNode(node) for node in reversed(self._graph.dfsOnDiscover(startNodes=[startNode], reverse=True)[0])] for node in reversed(nodes):
self.removeNode(node)
@Slot(Attribute, Attribute) @Slot(Attribute, Attribute)
def addEdge(self, src, dst): def addEdge(self, src, dst):

View file

@ -82,16 +82,18 @@ RowLayout {
keys: [inputDragTarget.objectName] keys: [inputDragTarget.objectName]
onEntered: { onEntered: {
// Filter drops: // Check if attributes are compatible to create a valid connection
if( root.readOnly if( root.readOnly // cannot connect on a read-only attribute
|| drag.source.objectName != inputDragTarget.objectName // not an edge connector || drag.source.objectName != inputDragTarget.objectName // not an edge connector
|| drag.source.nodeItem == inputDragTarget.nodeItem // connection between attributes of the same node || drag.source.baseType != inputDragTarget.baseType // not the same base type
|| inputDragTarget.attribute.isLink // already connected attribute || drag.source.nodeItem == inputDragTarget.nodeItem // connection between attributes of the same node
|| (drag.source.isList && !inputDragTarget.isList) // connection between a list and a simple attribute || inputDragTarget.attribute.isLink // already connected attribute
|| (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children || (drag.source.isList && !inputDragTarget.isList) // connection between a list and a simple attribute
|| drag.source.connectorType == "input" || (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children
) || drag.source.connectorType == "input" // refuse to connect an "input pin" on another one (input attr can be connected to input attr, but not the graphical pin)
)
{ {
// Refuse attributes connection
drag.accepted = false drag.accepted = false
} }
inputDropArea.acceptableDrop = drag.accepted inputDropArea.acceptableDrop = drag.accepted
@ -112,7 +114,8 @@ RowLayout {
readonly property string connectorType: "input" readonly property string connectorType: "input"
readonly property alias attribute: root.attribute readonly property alias attribute: root.attribute
readonly property alias nodeItem: root.nodeItem readonly property alias nodeItem: root.nodeItem
readonly property bool isOutput: attribute && attribute.isOutput readonly property bool isOutput: attribute.isOutput
readonly property string baseType: attribute.baseType
readonly property alias isList: root.isList readonly property alias isList: root.isList
property bool dragAccepted: false property bool dragAccepted: false
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -152,7 +155,7 @@ RowLayout {
point1y: inputDragTarget.y + inputDragTarget.height/2 point1y: inputDragTarget.y + inputDragTarget.height/2
point2x: parent.width / 2 point2x: parent.width / 2
point2y: parent.width / 2 point2y: parent.width / 2
color: nameLabel.color color: palette.highlight
thickness: outputDragTarget.dropAccepted ? 2 : 1 thickness: outputDragTarget.dropAccepted ? 2 : 1
} }
} }
@ -168,6 +171,7 @@ RowLayout {
Label { Label {
id: nameLabel id: nameLabel
enabled: !root.readOnly
property bool hovered: (inputConnectMA.containsMouse || inputConnectMA.drag.active || inputDropArea.containsDrag || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag) property bool hovered: (inputConnectMA.containsMouse || inputConnectMA.drag.active || inputDropArea.containsDrag || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag)
text: attribute ? attribute.label : "" text: attribute ? attribute.label : ""
elide: hovered ? Text.ElideNone : Text.ElideMiddle elide: hovered ? Text.ElideNone : Text.ElideMiddle
@ -219,15 +223,17 @@ RowLayout {
keys: [outputDragTarget.objectName] keys: [outputDragTarget.objectName]
onEntered: { onEntered: {
// Filter drops: // Check if attributes are compatible to create a valid connection
if( drag.source.objectName != outputDragTarget.objectName // not an edge connector if( drag.source.objectName != outputDragTarget.objectName // not an edge connector
|| drag.source.nodeItem == outputDragTarget.nodeItem // connection between attributes of the same node || drag.source.baseType != outputDragTarget.baseType // not the same base type
|| drag.source.attribute.isLink // already connected attribute || drag.source.nodeItem == outputDragTarget.nodeItem // connection between attributes of the same node
|| (!drag.source.isList && outputDragTarget.isList) // connection between a list and a simple attribute || drag.source.attribute.isLink // already connected attribute
|| (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children || (!drag.source.isList && outputDragTarget.isList) // connection between a list and a simple attribute
|| drag.source.connectorType == "output" || (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children
) || drag.source.connectorType == "output" // refuse to connect an output pin on another one
)
{ {
// Refuse attributes connection
drag.accepted = false drag.accepted = false
} }
outputDropArea.acceptableDrop = drag.accepted outputDropArea.acceptableDrop = drag.accepted
@ -249,6 +255,7 @@ RowLayout {
readonly property alias nodeItem: root.nodeItem readonly property alias nodeItem: root.nodeItem
readonly property bool isOutput: attribute.isOutput readonly property bool isOutput: attribute.isOutput
readonly property alias isList: root.isList readonly property alias isList: root.isList
readonly property string baseType: attribute.baseType
property bool dropAccepted: false property bool dropAccepted: false
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -283,7 +290,7 @@ RowLayout {
point1y: parent.width / 2 point1y: parent.width / 2
point2x: outputDragTarget.x + outputDragTarget.width/2 point2x: outputDragTarget.x + outputDragTarget.width/2
point2y: outputDragTarget.y + outputDragTarget.height/2 point2y: outputDragTarget.y + outputDragTarget.height/2
color: nameLabel.color color: palette.highlight
thickness: outputDragTarget.dropAccepted ? 2 : 1 thickness: outputDragTarget.dropAccepted ? 2 : 1
} }
} }

View file

@ -41,8 +41,12 @@ Shape {
startY: root.startY startY: root.startY
fillColor: "transparent" fillColor: "transparent"
strokeColor: "#3E3E3E" strokeColor: "#3E3E3E"
capStyle: ShapePath.RoundCap strokeStyle: edge != undefined && ((edge.src != undefined && edge.src.isOutput) || edge.dst == undefined) ? ShapePath.SolidLine : ShapePath.DashLine
strokeWidth: 1 strokeWidth: 1
// final visual width of this path (never below 1)
readonly property real visualWidth: Math.max(strokeWidth, 1)
dashPattern: [6/visualWidth, 4/visualWidth]
capStyle: ShapePath.RoundCap
PathCubic { PathCubic {
id: cubic id: cubic

View file

@ -236,10 +236,10 @@ Item {
model: nodeRepeater.loaded && root.graph ? root.graph.edges : undefined model: nodeRepeater.loaded && root.graph ? root.graph.edges : undefined
delegate: Edge { delegate: Edge {
property var src: edge ? root._attributeToDelegate[edge.src] : undefined property var src: root._attributeToDelegate[edge.src]
property var dst: edge ? root._attributeToDelegate[edge.dst] : undefined property var dst: root._attributeToDelegate[edge.dst]
property var srcAnchor: src.nodeItem.mapFromItem(src, src.outputAnchorPos.x, src.outputAnchorPos.y) property bool isValidEdge: src != undefined && dst != undefined
property var dstAnchor: dst.nodeItem.mapFromItem(dst, dst.inputAnchorPos.x, dst.inputAnchorPos.y) visible: isValidEdge
property bool inFocus: containsMouse || (edgeMenu.opened && edgeMenu.currentEdge == edge) property bool inFocus: containsMouse || (edgeMenu.opened && edgeMenu.currentEdge == edge)
@ -247,10 +247,10 @@ Item {
color: inFocus ? activePalette.highlight : activePalette.text color: inFocus ? activePalette.highlight : activePalette.text
thickness: inFocus ? 2 : 1 thickness: inFocus ? 2 : 1
opacity: 0.7 opacity: 0.7
point1x: src.nodeItem.x + srcAnchor.x point1x: isValidEdge ? src.globalX + src.outputAnchorPos.x : 0
point1y: src.nodeItem.y + srcAnchor.y point1y: isValidEdge ? src.globalY + src.outputAnchorPos.y : 0
point2x: dst.nodeItem.x + dstAnchor.x point2x: isValidEdge ? dst.globalX + dst.inputAnchorPos.x : 0
point2y: dst.nodeItem.y + dstAnchor.y point2y: isValidEdge ? dst.globalY + dst.inputAnchorPos.y : 0
onPressed: { onPressed: {
const canEdit = !edge.dst.node.locked const canEdit = !edge.dst.node.locked

View file

@ -27,6 +27,11 @@ Item {
readonly property color defaultColor: isCompatibilityNode ? "#444" : activePalette.base readonly property color defaultColor: isCompatibilityNode ? "#444" : activePalette.base
property color baseColor: defaultColor property color baseColor: defaultColor
Item {
id: m
property bool displayParams: false
}
// Mouse interaction related signals // Mouse interaction related signals
signal pressed(var mouse) signal pressed(var mouse)
signal doubleClicked(var mouse) signal doubleClicked(var mouse)
@ -60,7 +65,7 @@ Item {
} }
// Whether an attribute can be displayed as an attribute pin on the node // Whether an attribute can be displayed as an attribute pin on the node
function isDisplayableAsPin(attribute) { function isFileAttributeBaseType(attribute) {
// ATM, only File attributes are meant to be connected // ATM, only File attributes are meant to be connected
// TODO: review this if we want to connect something else // TODO: review this if we want to connect something else
return attribute.type == "File" return attribute.type == "File"
@ -110,7 +115,7 @@ Item {
// Selection border // Selection border
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: nodeContent
anchors.margins: -border.width anchors.margins: -border.width
visible: root.selected || root.hovered visible: root.selected || root.hovered
border.width: 2.5 border.width: 2.5
@ -120,10 +125,9 @@ Item {
color: "transparent" color: "transparent"
} }
// Background
Rectangle { Rectangle {
id: background id: background
anchors.fill: parent anchors.fill: nodeContent
color: Qt.lighter(activePalette.base, 1.4) color: Qt.lighter(activePalette.base, 1.4)
layer.enabled: true layer.enabled: true
layer.effect: DropShadow { radius: 3; color: shadowColor } layer.effect: DropShadow { radius: 3; color: shadowColor }
@ -131,192 +135,283 @@ Item {
opacity: 0.7 opacity: 0.7
} }
// Data Layout Rectangle {
Column { id: nodeContent
id: body
width: parent.width width: parent.width
height: childrenRect.height
color: "transparent"
// Header // Data Layout
Rectangle { Column {
id: header id: body
width: parent.width width: parent.width
height: headerLayout.height
color: root.selected ? activePalette.highlight : root.baseColor
radius: background.radius
// Fill header's bottom radius // Header
Rectangle { Rectangle {
id: header
width: parent.width width: parent.width
height: parent.radius height: headerLayout.height
anchors.bottom: parent.bottom color: root.selected ? activePalette.highlight : root.baseColor
color: parent.color radius: background.radius
z: -1
}
// Header Layout // Fill header's bottom radius
RowLayout { Rectangle {
id: headerLayout width: parent.width
width: parent.width height: parent.radius
spacing: 0 anchors.bottom: parent.bottom
color: parent.color
// Node Name z: -1
Label {
Layout.fillWidth: true
text: node ? node.label : ""
padding: 4
color: root.selected ? "white" : activePalette.text
elide: Text.ElideMiddle
font.pointSize: 8
} }
// Node State icons // Header Layout
RowLayout { RowLayout {
Layout.fillWidth: true id: headerLayout
Layout.alignment: Qt.AlignRight width: parent.width
Layout.rightMargin: 2 spacing: 0
spacing: 2
// CompatibilityBadge icon for CompatibilityNodes // Node Name
Loader { Label {
active: root.isCompatibilityNode Layout.fillWidth: true
sourceComponent: CompatibilityBadge { text: node ? node.label : ""
sourceComponent: iconDelegate padding: 4
canUpgrade: root.node.canUpgrade color: root.selected ? "white" : activePalette.text
issueDetails: root.node.issueDetails elide: Text.ElideMiddle
font.pointSize: 8
}
// Node State icons
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2
spacing: 2
// CompatibilityBadge icon for CompatibilityNodes
Loader {
active: root.isCompatibilityNode
sourceComponent: CompatibilityBadge {
sourceComponent: iconDelegate
canUpgrade: root.node.canUpgrade
issueDetails: root.node.issueDetails
}
} }
}
// Data sharing indicator // Data sharing indicator
// Note: for an unknown reason, there are some performance issues with the UI refresh. // Note: for an unknown reason, there are some performance issues with the UI refresh.
// Example: a node duplicated 40 times will be slow while creating another identical node // Example: a node duplicated 40 times will be slow while creating another identical node
// (sharing the same uid) will not be as slow. If save, quit and reload, it will become slow. // (sharing the same uid) will not be as slow. If save, quit and reload, it will become slow.
MaterialToolButton { MaterialToolButton {
property string baseText: "<b>Shares internal folder (data) with other node(s). Hold click for details.</b>" property string baseText: "<b>Shares internal folder (data) with other node(s). Hold click for details.</b>"
property string toolTipText: visible ? baseText : "" property string toolTipText: visible ? baseText : ""
visible: node.hasDuplicates visible: node.hasDuplicates
text: MaterialIcons.layers text: MaterialIcons.layers
font.pointSize: 7 font.pointSize: 7
padding: 2 padding: 2
palette.text: Colors.sysPalette.text palette.text: Colors.sysPalette.text
ToolTip.text: toolTipText ToolTip.text: toolTipText
onPressed: { offsetReleased.running = false; toolTipText = visible ? generateDuplicateList() : "" } onPressed: { offsetReleased.running = false; toolTipText = visible ? generateDuplicateList() : "" }
onReleased: { toolTipText = "" ; offsetReleased.running = true } onReleased: { toolTipText = "" ; offsetReleased.running = true }
onCanceled: released() onCanceled: released()
// Used for a better user experience with the button // Used for a better user experience with the button
// Avoid to change the text too quickly // Avoid to change the text too quickly
Timer { Timer {
id: offsetReleased id: offsetReleased
interval: 750; running: false; repeat: false interval: 750; running: false; repeat: false
onTriggered: parent.toolTipText = visible ? parent.baseText : "" onTriggered: parent.toolTipText = visible ? parent.baseText : ""
}
} }
}
// Submitted externally indicator // Submitted externally indicator
MaterialLabel { MaterialLabel {
visible: ["SUBMITTED", "RUNNING"].includes(node.globalStatus) && node.chunks.count > 0 && node.globalExecMode === "EXTERN" visible: ["SUBMITTED", "RUNNING"].includes(node.globalStatus) && node.chunks.count > 0 && node.globalExecMode === "EXTERN"
text: MaterialIcons.cloud text: MaterialIcons.cloud
padding: 2 padding: 2
font.pointSize: 7 font.pointSize: 7
palette.text: Colors.sysPalette.text palette.text: Colors.sysPalette.text
ToolTip.text: "Computed Externally" ToolTip.text: "Computed Externally"
} }
// Lock indicator // Lock indicator
MaterialLabel { MaterialLabel {
visible: root.readOnly visible: root.readOnly
text: MaterialIcons.lock text: MaterialIcons.lock
padding: 2 padding: 2
font.pointSize: 7 font.pointSize: 7
palette.text: "red" palette.text: "red"
ToolTip.text: "Locked" ToolTip.text: "Locked"
}
} }
} }
} }
}
// Node Chunks // Node Chunks
NodeChunks { NodeChunks {
defaultColor: Colors.sysPalette.mid defaultColor: Colors.sysPalette.mid
implicitHeight: 3 implicitHeight: 3
width: parent.width width: parent.width
model: node ? node.chunks : undefined model: node ? node.chunks : undefined
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Colors.sysPalette.mid color: Colors.sysPalette.mid
z: -1 z: -1
}
} }
}
// Vertical Spacer // Vertical Spacer
Item { width: parent.width; height: 2 } Item { width: parent.width; height: 2 }
// Input/Output Attributes // Input/Output Attributes
Item { Item {
id: nodeAttributes id: nodeAttributes
width: parent.width - 2 width: parent.width - 2
height: childrenRect.height height: childrenRect.height
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
enabled: !root.readOnly && !root.isCompatibilityNode enabled: !root.isCompatibilityNode
Column {
width: parent.width
spacing: 5
bottomPadding: 2
Column { Column {
id: outputs id: attributesColumn
width: parent.width width: parent.width
spacing: 3 spacing: 5
Repeater { bottomPadding: 2
model: node ? node.attributes : undefined
delegate: Loader { Column {
id: outputLoader id: outputs
active: object.isOutput && isDisplayableAsPin(object) width: parent.width
anchors.right: parent.right spacing: 3
width: outputs.width Repeater {
model: node ? node.attributes : undefined
sourceComponent: AttributePin { delegate: Loader {
id: outPin id: outputLoader
nodeItem: root active: object.isOutput && isFileAttributeBaseType(object)
attribute: object anchors.right: parent.right
width: outputs.width
readOnly: root.readOnly sourceComponent: AttributePin {
onPressed: root.pressed(mouse) id: outPin
Component.onCompleted: attributePinCreated(object, outPin) nodeItem: root
Component.onDestruction: attributePinDeleted(attribute, outPin) attribute: object
property real globalX: root.x + nodeAttributes.x + outputs.x + outputLoader.x + outPin.x
property real globalY: root.y + nodeAttributes.y + outputs.y + outputLoader.y + outPin.y
onPressed: root.pressed(mouse)
Component.onCompleted: attributePinCreated(object, outPin)
Component.onDestruction: attributePinDeleted(attribute, outPin)
}
} }
} }
} }
}
Column { Column {
id: inputs id: inputs
width: parent.width width: parent.width
spacing: 3 spacing: 3
Repeater { Repeater {
model: node ? node.attributes : undefined model: node ? node.attributes : undefined
delegate: Loader { delegate: Loader {
active: !object.isOutput && isDisplayableAsPin(object) id: inputLoader
width: inputs.width active: !object.isOutput && isFileAttributeBaseType(object)
width: inputs.width
sourceComponent: AttributePin { sourceComponent: AttributePin {
id: inPin id: inPin
nodeItem: root nodeItem: root
attribute: object attribute: object
readOnly: root.readOnly
Component.onCompleted: attributePinCreated(attribute, inPin) property real globalX: root.x + nodeAttributes.x + inputs.x + inputLoader.x + inPin.x
Component.onDestruction: attributePinDeleted(attribute, inPin) property real globalY: root.y + nodeAttributes.y + inputs.y + inputLoader.y + inPin.y
onPressed: root.pressed(mouse)
onChildPinCreated: attributePinCreated(childAttribute, inPin) readOnly: root.readOnly
onChildPinDeleted: attributePinDeleted(childAttribute, inPin) Component.onCompleted: attributePinCreated(attribute, inPin)
Component.onDestruction: attributePinDeleted(attribute, inPin)
onPressed: root.pressed(mouse)
onChildPinCreated: attributePinCreated(childAttribute, inPin)
onChildPinDeleted: attributePinDeleted(childAttribute, inPin)
}
} }
} }
} }
// Vertical Spacer
Rectangle {
height: inputParams.height > 0 ? 3 : 0
visible: (height == 3)
Behavior on height { PropertyAnimation {easing.type: Easing.Linear} }
width: parent.width
color: Colors.sysPalette.mid
MaterialToolButton {
text: " "
width: parent.width
height: parent.height
padding: 0
spacing: 0
anchors.margins: 0
font.pointSize: 6
onClicked: {
m.displayParams = ! m.displayParams
}
}
}
Rectangle {
id: inputParamsRect
width: parent.width
height: childrenRect.height
color: "transparent"
Column {
id: inputParams
width: parent.width
spacing: 3
Repeater {
id: inputParamsRepeater
model: node ? node.attributes : undefined
delegate: Loader {
id: paramLoader
active: !object.isOutput && !isFileAttributeBaseType(object)
property bool isFullyActive: (m.displayParams || object.isLink || object.hasOutputConnections)
width: parent.width
sourceComponent: AttributePin {
id: inPin
nodeItem: root
property real globalX: root.x + nodeAttributes.x + inputParamsRect.x + paramLoader.x + inPin.x
property real globalY: root.y + nodeAttributes.y + inputParamsRect.y + paramLoader.y + inPin.y
height: isFullyActive ? childrenRect.height : 0
Behavior on height { PropertyAnimation {easing.type: Easing.Linear} }
visible: (height == childrenRect.height)
attribute: object
readOnly: root.readOnly
Component.onCompleted: attributePinCreated(attribute, inPin)
Component.onDestruction: attributePinDeleted(attribute, inPin)
onPressed: root.pressed(mouse)
onChildPinCreated: attributePinCreated(childAttribute, inPin)
onChildPinDeleted: attributePinDeleted(childAttribute, inPin)
}
}
}
}
}
MaterialToolButton {
text: root.hovered ? (m.displayParams ? MaterialIcons.arrow_drop_up : MaterialIcons.arrow_drop_down) : " "
Layout.alignment: Qt.AlignBottom
width: parent.width
height: 5
padding: 0
spacing: 0
anchors.margins: 0
font.pointSize: 10
onClicked: {
m.displayParams = ! m.displayParams
}
}
} }
} }
} }

View file

@ -129,7 +129,52 @@ FloatingPane {
id: searchBar id: searchBar
Layout.fillWidth: true Layout.fillWidth: true
} }
RowLayout {
Layout.alignment: Qt.AlignHCenter
Label {
font.family: MaterialIcons.fontFamily
text: MaterialIcons.shutter_speed
}
Label {
id: exposureLabel
text: {
if(metadata["ExposureTime"] === undefined)
return "";
var expStr = metadata["ExposureTime"];
var exp = parseFloat(expStr);
if(exp < 1.0)
{
var invExp = 1.0 / exp;
return "1/" + invExp.toFixed(0);
}
return expStr;
}
elide: Text.ElideRight
horizontalAlignment: Text.AlignHLeft
}
Item { width: 4 }
Label {
font.family: MaterialIcons.fontFamily
text: MaterialIcons.camera
}
Label {
id: fnumberLabel
text: (metadata["FNumber"] !== undefined) ? ("f/" + metadata["FNumber"]) : ""
elide: Text.ElideRight
horizontalAlignment: Text.AlignHLeft
}
Item { width: 4 }
Label {
font.family: MaterialIcons.fontFamily
text: MaterialIcons.iso
}
Label {
id: isoLabel
text: metadata["Exif:ISOSpeedRatings"] || ""
elide: Text.ElideRight
horizontalAlignment: Text.AlignHLeft
}
}
// Metadata ListView // Metadata ListView
ListView { ListView {
id: metadataView id: metadataView

View file

@ -123,6 +123,8 @@ FocusScope {
} }
function getImageFile(type) { function getImageFile(type) {
if(!_reconstruction.activeNodes)
return "";
var depthMapNode = _reconstruction.activeNodes.get('allDepthMap').node; var depthMapNode = _reconstruction.activeNodes.get('allDepthMap').node;
if (type == "image") { if (type == "image") {
return root.source; return root.source;
@ -240,8 +242,7 @@ FocusScope {
} }
// Image cache of the last loaded image // Image cache of the last loaded image
// Only visible when the main one is loading, to keep an image // Only visible when the main one is loading, to maintain a displayed image for smoother transitions
// displayed at all time and smoothen transitions
Image { Image {
id: qtImageViewerCache id: qtImageViewerCache