mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-22 13:36:31 +02:00
Merge pull request #1182 from alicevision/dev/graphDeps
Graph Dependencies
This commit is contained in:
commit
c502ee1e73
15 changed files with 448 additions and 254 deletions
|
@ -93,6 +93,9 @@ class Attribute(BaseObject):
|
|||
def getType(self):
|
||||
return self.attributeDesc.__class__.__name__
|
||||
|
||||
def getBaseType(self):
|
||||
return self.getType()
|
||||
|
||||
def getLabel(self):
|
||||
return self._label
|
||||
|
||||
|
@ -137,7 +140,7 @@ class Attribute(BaseObject):
|
|||
self.valueChanged.emit()
|
||||
|
||||
def resetValue(self):
|
||||
self._value = ""
|
||||
self._value = self.attributeDesc.value
|
||||
|
||||
def requestGraphUpdate(self):
|
||||
if self.node.graph:
|
||||
|
@ -258,6 +261,7 @@ class Attribute(BaseObject):
|
|||
fullName = Property(str, getFullName, constant=True)
|
||||
label = Property(str, getLabel, constant=True)
|
||||
type = Property(str, getType, constant=True)
|
||||
baseType = Property(str, getType, constant=True)
|
||||
desc = Property(desc.Attribute, lambda self: self.attributeDesc, constant=True)
|
||||
valueChanged = Signal()
|
||||
value = Property(Variant, _get_value, _set_value, notify=valueChanged)
|
||||
|
@ -292,6 +296,9 @@ class ListAttribute(Attribute):
|
|||
def __len__(self):
|
||||
return len(self._value)
|
||||
|
||||
def getBaseType(self):
|
||||
return self.attributeDesc.elementDesc.__class__.__name__
|
||||
|
||||
def at(self, idx):
|
||||
""" Returns child attribute at index 'idx' """
|
||||
# implement 'at' rather than '__getitem__'
|
||||
|
@ -396,6 +403,7 @@ class ListAttribute(Attribute):
|
|||
# Override value property setter
|
||||
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
||||
isDefault = Property(bool, _isDefault, notify=Attribute.valueChanged)
|
||||
baseType = Property(str, getBaseType, constant=True)
|
||||
|
||||
|
||||
class GroupAttribute(Attribute):
|
||||
|
|
|
@ -85,9 +85,10 @@ class Visitor(object):
|
|||
Base class for Graph Visitors that does nothing.
|
||||
Sub-classes can override any method to implement specific algorithms.
|
||||
"""
|
||||
def __init__(self, reverse):
|
||||
def __init__(self, reverse, dependenciesOnly):
|
||||
super(Visitor, self).__init__()
|
||||
self.reverse = reverse
|
||||
self.dependenciesOnly = dependenciesOnly
|
||||
|
||||
# def initializeVertex(self, s, g):
|
||||
# '''is invoked on every vertex of the graph before the start of the graph search.'''
|
||||
|
@ -383,7 +384,7 @@ class Graph(BaseObject):
|
|||
Returns:
|
||||
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
|
||||
duplicates = OrderedDict()
|
||||
|
||||
|
@ -581,13 +582,13 @@ class Graph(BaseObject):
|
|||
def edge(self, dstAttributeName):
|
||||
return self._edges.get(dstAttributeName)
|
||||
|
||||
def getLeafNodes(self):
|
||||
nodesWithOutput = set([edge.src.node for edge in self.edges])
|
||||
return set(self._nodes) - nodesWithOutput
|
||||
def getLeafNodes(self, dependenciesOnly):
|
||||
nodesWithOutputLink = set([edge.src.node for edge in self.getEdges(dependenciesOnly)])
|
||||
return set(self._nodes) - nodesWithOutputLink
|
||||
|
||||
def getRootNodes(self):
|
||||
nodesWithInput = set([edge.dst.node for edge in self.edges])
|
||||
return set(self._nodes) - nodesWithInput
|
||||
def getRootNodes(self, dependenciesOnly):
|
||||
nodesWithInputLink = set([edge.dst.node for edge in self.getEdges(dependenciesOnly)])
|
||||
return set(self._nodes) - nodesWithInputLink
|
||||
|
||||
@changeTopology
|
||||
def addEdge(self, srcAttr, dstAttr):
|
||||
|
@ -635,21 +636,21 @@ class Graph(BaseObject):
|
|||
minDepth, maxDepth = self._nodesMinMaxDepths[node]
|
||||
return minDepth if minimal else maxDepth
|
||||
|
||||
def getInputEdges(self, node):
|
||||
return set([edge for edge in self.edges if edge.dst.node is node])
|
||||
def getInputEdges(self, node, dependenciesOnly):
|
||||
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)
|
||||
|
||||
for edge in self.edges:
|
||||
for edge in self.getEdges(dependenciesOnly=dependenciesOnly):
|
||||
nodeEdges[edge.dst.node].add(edge.src.node)
|
||||
|
||||
return nodeEdges
|
||||
|
||||
def _getOutputEdgesPerNode(self):
|
||||
def _getOutputEdgesPerNode(self, dependenciesOnly):
|
||||
nodeEdges = defaultdict(set)
|
||||
|
||||
for edge in self.edges:
|
||||
for edge in self.getEdges(dependenciesOnly=dependenciesOnly):
|
||||
nodeEdges[edge.src.node].add(edge.dst.node)
|
||||
|
||||
return nodeEdges
|
||||
|
@ -657,7 +658,7 @@ class Graph(BaseObject):
|
|||
def dfs(self, visitor, startNodes=None, longestPathFirst=False):
|
||||
# Default direction (visitor.reverse=False): from node to root
|
||||
# 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
|
||||
colors = {}
|
||||
for u in self._nodes:
|
||||
|
@ -668,7 +669,7 @@ class Graph(BaseObject):
|
|||
# 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.")
|
||||
|
||||
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:
|
||||
# Graph topology must be known and node depths up-to-date
|
||||
|
@ -711,7 +712,7 @@ class Graph(BaseObject):
|
|||
colors[u] = BLACK
|
||||
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.
|
||||
Order is defined by the visit and finishVertex event.
|
||||
|
@ -728,13 +729,13 @@ class Graph(BaseObject):
|
|||
"""
|
||||
nodes = []
|
||||
edges = []
|
||||
visitor = Visitor(reverse=reverse)
|
||||
visitor = Visitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
|
||||
visitor.finishVertex = lambda vertex, graph: nodes.append(vertex)
|
||||
visitor.finishEdge = lambda edge, graph: edges.append(edge)
|
||||
self.dfs(visitor=visitor, startNodes=startNodes, longestPathFirst=longestPathFirst)
|
||||
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.
|
||||
Order is defined by the visit and discoverVertex event.
|
||||
|
@ -753,7 +754,7 @@ class Graph(BaseObject):
|
|||
"""
|
||||
nodes = []
|
||||
edges = []
|
||||
visitor = Visitor(reverse=reverse)
|
||||
visitor = Visitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
|
||||
|
||||
def discoverVertex(vertex, graph):
|
||||
if not filterTypes or vertex.nodeType in filterTypes:
|
||||
|
@ -777,7 +778,7 @@ class Graph(BaseObject):
|
|||
"""
|
||||
nodes = []
|
||||
edges = []
|
||||
visitor = Visitor(reverse=False)
|
||||
visitor = Visitor(reverse=False, dependenciesOnly=True)
|
||||
|
||||
def discoverVertex(vertex, graph):
|
||||
if vertex.hasStatus(Status.SUCCESS):
|
||||
|
@ -832,7 +833,7 @@ class Graph(BaseObject):
|
|||
self._computationBlocked.clear()
|
||||
|
||||
compatNodes = []
|
||||
visitor = Visitor(reverse=False)
|
||||
visitor = Visitor(reverse=False, dependenciesOnly=True)
|
||||
|
||||
def discoverVertex(vertex, graph):
|
||||
# initialize depths
|
||||
|
@ -866,7 +867,7 @@ class Graph(BaseObject):
|
|||
# propagate inputVertex computability
|
||||
self._computationBlocked[currentVertex] |= self._computationBlocked[inputVertex]
|
||||
|
||||
leaves = self.getLeafNodes()
|
||||
leaves = self.getLeafNodes(visitor.dependenciesOnly)
|
||||
visitor.finishEdge = finishEdge
|
||||
visitor.discoverVertex = discoverVertex
|
||||
self.dfs(visitor=visitor, startNodes=leaves)
|
||||
|
@ -890,7 +891,7 @@ class Graph(BaseObject):
|
|||
"""
|
||||
nodesStack = []
|
||||
edgesScore = defaultdict(lambda: 0)
|
||||
visitor = Visitor(reverse=False)
|
||||
visitor = Visitor(reverse=False, dependenciesOnly=False)
|
||||
|
||||
def finishEdge(edge, graph):
|
||||
u, v = edge
|
||||
|
@ -926,18 +927,34 @@ class Graph(BaseObject):
|
|||
flowEdges.append(link)
|
||||
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. """
|
||||
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)
|
||||
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. """
|
||||
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)
|
||||
return outputNodes[1:] # exclude current node
|
||||
|
@ -957,8 +974,8 @@ class Graph(BaseObject):
|
|||
return 0
|
||||
|
||||
class SCVisitor(Visitor):
|
||||
def __init__(self, reverse):
|
||||
super(SCVisitor, self).__init__(reverse)
|
||||
def __init__(self, reverse, dependenciesOnly):
|
||||
super(SCVisitor, self).__init__(reverse, dependenciesOnly)
|
||||
|
||||
canCompute = True
|
||||
canSubmit = True
|
||||
|
@ -969,7 +986,7 @@ class Graph(BaseObject):
|
|||
if vertex.isExtern():
|
||||
self.canCompute = False
|
||||
|
||||
visitor = SCVisitor(reverse=False)
|
||||
visitor = SCVisitor(reverse=False, dependenciesOnly=True)
|
||||
self.dfs(visitor=visitor, startNodes=[startNode])
|
||||
return visitor.canCompute + (2 * visitor.canSubmit)
|
||||
|
||||
|
@ -1131,7 +1148,7 @@ class Graph(BaseObject):
|
|||
|
||||
@Slot(Node)
|
||||
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()
|
||||
|
||||
def iterChunksByStatus(self, status):
|
||||
|
|
|
@ -589,11 +589,11 @@ class BaseNode(BaseObject):
|
|||
def minDepth(self):
|
||||
return self.graph.getDepth(self, minimal=True)
|
||||
|
||||
def getInputNodes(self, recursive=False):
|
||||
return self.graph.getInputNodes(self, recursive=recursive)
|
||||
def getInputNodes(self, recursive, dependenciesOnly):
|
||||
return self.graph.getInputNodes(self, recursive=recursive, dependenciesOnly=dependenciesOnly)
|
||||
|
||||
def getOutputNodes(self, recursive=False):
|
||||
return self.graph.getOutputNodes(self, recursive=recursive)
|
||||
def getOutputNodes(self, recursive, dependenciesOnly):
|
||||
return self.graph.getOutputNodes(self, recursive=recursive, dependenciesOnly=dependenciesOnly)
|
||||
|
||||
def toDict(self):
|
||||
pass
|
||||
|
@ -883,7 +883,7 @@ class BaseNode(BaseObject):
|
|||
# Warning: we must handle some specific cases for global start/stop
|
||||
if self._locked and currentStatus in (Status.ERROR, Status.STOPPED, Status.NONE):
|
||||
self.setLocked(False)
|
||||
inputNodes = self.getInputNodes(recursive=True)
|
||||
inputNodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
|
||||
|
||||
for node in inputNodes:
|
||||
if node.getGlobalStatus() == Status.RUNNING:
|
||||
|
@ -901,8 +901,8 @@ class BaseNode(BaseObject):
|
|||
|
||||
if currentStatus == Status.SUCCESS:
|
||||
# At this moment, the node is necessarily locked because of previous if statement
|
||||
inputNodes = self.getInputNodes(recursive=True)
|
||||
outputNodes = self.getOutputNodes(recursive=True)
|
||||
inputNodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
|
||||
outputNodes = self.getOutputNodes(recursive=True, dependenciesOnly=True)
|
||||
stayLocked = None
|
||||
|
||||
# Check if at least one dependentNode is submitted or currently running
|
||||
|
@ -918,7 +918,7 @@ class BaseNode(BaseObject):
|
|||
return
|
||||
elif currentStatus in lockedStatus and self._chunks.at(0).statusNodeName == self.name:
|
||||
self.setLocked(True)
|
||||
inputNodes = self.getInputNodes(recursive=True)
|
||||
inputNodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
|
||||
for node in inputNodes:
|
||||
node.setLocked(True)
|
||||
return
|
||||
|
|
|
@ -147,7 +147,7 @@ class TaskManager(BaseObject):
|
|||
self.removeNode(node, displayList=False, processList=True)
|
||||
|
||||
# Remove output nodes from display and computing lists
|
||||
outputNodes = node.getOutputNodes(recursive=True)
|
||||
outputNodes = node.getOutputNodes(recursive=True, dependenciesOnly=True)
|
||||
for n in outputNodes:
|
||||
if n.getGlobalStatus() in (Status.ERROR, Status.SUBMITTED):
|
||||
n.upgradeStatusTo(Status.NONE)
|
||||
|
@ -184,7 +184,7 @@ class TaskManager(BaseObject):
|
|||
else:
|
||||
# Check dependencies of toNodes
|
||||
if not toNodes:
|
||||
toNodes = graph.getLeafNodes()
|
||||
toNodes = graph.getLeafNodes(dependenciesOnly=True)
|
||||
toNodes = list(toNodes)
|
||||
allReady = self.checkNodesDependencies(graph, toNodes, "COMPUTATION")
|
||||
|
||||
|
@ -402,7 +402,7 @@ class TaskManager(BaseObject):
|
|||
|
||||
# Check dependencies of toNodes
|
||||
if not toNodes:
|
||||
toNodes = graph.getLeafNodes()
|
||||
toNodes = graph.getLeafNodes(dependenciesOnly=True)
|
||||
toNodes = list(toNodes)
|
||||
allReady = self.checkNodesDependencies(graph, toNodes, "SUBMITTING")
|
||||
|
||||
|
|
|
@ -210,10 +210,16 @@ def panoramaHdrPipeline(graph):
|
|||
|
||||
ldr2hdrCalibration = graph.addNewNode('LdrToHdrCalibration',
|
||||
input=ldr2hdrSampling.input,
|
||||
userNbBrackets=ldr2hdrSampling.userNbBrackets,
|
||||
byPass=ldr2hdrSampling.byPass,
|
||||
channelQuantizationPower=ldr2hdrSampling.channelQuantizationPower,
|
||||
samples=ldr2hdrSampling.output)
|
||||
|
||||
ldr2hdrMerge = graph.addNewNode('LdrToHdrMerge',
|
||||
input=ldr2hdrCalibration.input,
|
||||
userNbBrackets=ldr2hdrCalibration.userNbBrackets,
|
||||
byPass=ldr2hdrCalibration.byPass,
|
||||
channelQuantizationPower=ldr2hdrCalibration.channelQuantizationPower,
|
||||
response=ldr2hdrCalibration.response)
|
||||
|
||||
featureExtraction = graph.addNewNode('FeatureExtraction',
|
||||
|
@ -233,12 +239,14 @@ def panoramaHdrPipeline(graph):
|
|||
featureMatching = graph.addNewNode('FeatureMatching',
|
||||
input=imageMatching.input,
|
||||
featuresFolders=imageMatching.featuresFolders,
|
||||
imagePairsList=imageMatching.output)
|
||||
imagePairsList=imageMatching.output,
|
||||
describerTypes=featureExtraction.describerTypes)
|
||||
|
||||
panoramaEstimation = graph.addNewNode('PanoramaEstimation',
|
||||
input=featureMatching.input,
|
||||
featuresFolders=featureMatching.featuresFolders,
|
||||
matchesFolders=[featureMatching.output])
|
||||
input=featureMatching.input,
|
||||
featuresFolders=featureMatching.featuresFolders,
|
||||
matchesFolders=[featureMatching.output],
|
||||
describerTypes=featureMatching.describerTypes)
|
||||
|
||||
panoramaOrientation = graph.addNewNode('SfMTransform',
|
||||
input=panoramaEstimation.output,
|
||||
|
@ -340,11 +348,13 @@ def sfmPipeline(graph):
|
|||
featureMatching = graph.addNewNode('FeatureMatching',
|
||||
input=imageMatching.input,
|
||||
featuresFolders=imageMatching.featuresFolders,
|
||||
imagePairsList=imageMatching.output)
|
||||
imagePairsList=imageMatching.output,
|
||||
describerTypes=featureExtraction.describerTypes)
|
||||
structureFromMotion = graph.addNewNode('StructureFromMotion',
|
||||
input=featureMatching.input,
|
||||
featuresFolders=featureMatching.featuresFolders,
|
||||
matchesFolders=[featureMatching.output])
|
||||
matchesFolders=[featureMatching.output],
|
||||
describerTypes=featureMatching.describerTypes)
|
||||
return [
|
||||
cameraInit,
|
||||
featureExtraction,
|
||||
|
@ -419,16 +429,18 @@ def sfmAugmentation(graph, sourceSfm, withMVS=False):
|
|||
featureMatching = graph.addNewNode('FeatureMatching',
|
||||
input=imageMatchingMulti.outputCombinedSfM,
|
||||
featuresFolders=imageMatchingMulti.featuresFolders,
|
||||
imagePairsList=imageMatchingMulti.output)
|
||||
imagePairsList=imageMatchingMulti.output,
|
||||
describerTypes=featureExtraction.describerTypes)
|
||||
structureFromMotion = graph.addNewNode('StructureFromMotion',
|
||||
input=featureMatching.input,
|
||||
featuresFolders=featureMatching.featuresFolders,
|
||||
matchesFolders=[featureMatching.output])
|
||||
matchesFolders=[featureMatching.output],
|
||||
describerTypes=featureMatching.describerTypes)
|
||||
graph.addEdge(sourceSfm.output, imageMatchingMulti.inputB)
|
||||
|
||||
sfmNodes = [
|
||||
cameraInit,
|
||||
featureMatching,
|
||||
featureExtraction,
|
||||
imageMatchingMulti,
|
||||
featureMatching,
|
||||
structureFromMotion
|
||||
|
|
|
@ -49,6 +49,23 @@ class LdrToHdrCalibration(desc.CommandLineNode):
|
|||
value=desc.Node.internalFolder,
|
||||
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(
|
||||
name='byPass',
|
||||
label='Bypass',
|
||||
|
@ -87,23 +104,6 @@ class LdrToHdrCalibration(desc.CommandLineNode):
|
|||
uid=[0],
|
||||
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(
|
||||
name='channelQuantizationPower',
|
||||
label='Channel Quantization Power',
|
||||
|
|
|
@ -44,7 +44,7 @@ Compute the image warping for each input image in the panorama coordinate system
|
|||
),
|
||||
desc.IntParam(
|
||||
name='percentUpscale',
|
||||
label='Upscale ratio',
|
||||
label='Upscale Ratio',
|
||||
description='Percentage of upscaled pixels.\n'
|
||||
'\n'
|
||||
'How many percent of the pixels will be upscaled (compared to its original resolution):\n'
|
||||
|
|
|
@ -227,6 +227,9 @@ class AddEdgeCommand(GraphCommand):
|
|||
self.dstAttr = dst.getFullName()
|
||||
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):
|
||||
self.graph.addEdge(self.graph.attribute(self.srcAttr), self.graph.attribute(self.dstAttr))
|
||||
return True
|
||||
|
|
|
@ -411,7 +411,7 @@ class UIGraph(QObject):
|
|||
node.clearSubmittedChunks()
|
||||
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()
|
||||
self._taskManager.removeNode(n, displayList=True, processList=True)
|
||||
|
||||
|
@ -524,9 +524,11 @@ class UIGraph(QObject):
|
|||
startNode (Node): the node to start from.
|
||||
"""
|
||||
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
|
||||
# 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)
|
||||
def addEdge(self, src, dst):
|
||||
|
|
|
@ -82,16 +82,18 @@ RowLayout {
|
|||
|
||||
keys: [inputDragTarget.objectName]
|
||||
onEntered: {
|
||||
// Filter drops:
|
||||
if( root.readOnly
|
||||
|| drag.source.objectName != inputDragTarget.objectName // not an edge connector
|
||||
|| drag.source.nodeItem == inputDragTarget.nodeItem // connection between attributes of the same node
|
||||
|| inputDragTarget.attribute.isLink // already connected attribute
|
||||
|| (drag.source.isList && !inputDragTarget.isList) // connection between a list and a simple attribute
|
||||
|| (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children
|
||||
|| drag.source.connectorType == "input"
|
||||
)
|
||||
// Check if attributes are compatible to create a valid connection
|
||||
if( root.readOnly // cannot connect on a read-only attribute
|
||||
|| drag.source.objectName != inputDragTarget.objectName // not an edge connector
|
||||
|| drag.source.baseType != inputDragTarget.baseType // not the same base type
|
||||
|| drag.source.nodeItem == inputDragTarget.nodeItem // connection between attributes of the same node
|
||||
|| inputDragTarget.attribute.isLink // already connected attribute
|
||||
|| (drag.source.isList && !inputDragTarget.isList) // connection between a list and a simple attribute
|
||||
|| (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
|
||||
}
|
||||
inputDropArea.acceptableDrop = drag.accepted
|
||||
|
@ -112,7 +114,8 @@ RowLayout {
|
|||
readonly property string connectorType: "input"
|
||||
readonly property alias attribute: root.attribute
|
||||
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
|
||||
property bool dragAccepted: false
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@ -152,7 +155,7 @@ RowLayout {
|
|||
point1y: inputDragTarget.y + inputDragTarget.height/2
|
||||
point2x: parent.width / 2
|
||||
point2y: parent.width / 2
|
||||
color: nameLabel.color
|
||||
color: palette.highlight
|
||||
thickness: outputDragTarget.dropAccepted ? 2 : 1
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +171,7 @@ RowLayout {
|
|||
Label {
|
||||
id: nameLabel
|
||||
|
||||
enabled: !root.readOnly
|
||||
property bool hovered: (inputConnectMA.containsMouse || inputConnectMA.drag.active || inputDropArea.containsDrag || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag)
|
||||
text: attribute ? attribute.label : ""
|
||||
elide: hovered ? Text.ElideNone : Text.ElideMiddle
|
||||
|
@ -219,15 +223,17 @@ RowLayout {
|
|||
|
||||
keys: [outputDragTarget.objectName]
|
||||
onEntered: {
|
||||
// Filter drops:
|
||||
if( drag.source.objectName != outputDragTarget.objectName // not an edge connector
|
||||
|| drag.source.nodeItem == outputDragTarget.nodeItem // connection between attributes of the same node
|
||||
|| drag.source.attribute.isLink // already connected attribute
|
||||
|| (!drag.source.isList && outputDragTarget.isList) // connection between a list and a simple attribute
|
||||
|| (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children
|
||||
|| drag.source.connectorType == "output"
|
||||
)
|
||||
// Check if attributes are compatible to create a valid connection
|
||||
if( drag.source.objectName != outputDragTarget.objectName // not an edge connector
|
||||
|| drag.source.baseType != outputDragTarget.baseType // not the same base type
|
||||
|| drag.source.nodeItem == outputDragTarget.nodeItem // connection between attributes of the same node
|
||||
|| drag.source.attribute.isLink // already connected attribute
|
||||
|| (!drag.source.isList && outputDragTarget.isList) // connection between a list and a simple attribute
|
||||
|| (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
|
||||
}
|
||||
outputDropArea.acceptableDrop = drag.accepted
|
||||
|
@ -249,6 +255,7 @@ RowLayout {
|
|||
readonly property alias nodeItem: root.nodeItem
|
||||
readonly property bool isOutput: attribute.isOutput
|
||||
readonly property alias isList: root.isList
|
||||
readonly property string baseType: attribute.baseType
|
||||
property bool dropAccepted: false
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@ -283,7 +290,7 @@ RowLayout {
|
|||
point1y: parent.width / 2
|
||||
point2x: outputDragTarget.x + outputDragTarget.width/2
|
||||
point2y: outputDragTarget.y + outputDragTarget.height/2
|
||||
color: nameLabel.color
|
||||
color: palette.highlight
|
||||
thickness: outputDragTarget.dropAccepted ? 2 : 1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,12 @@ Shape {
|
|||
startY: root.startY
|
||||
fillColor: "transparent"
|
||||
strokeColor: "#3E3E3E"
|
||||
capStyle: ShapePath.RoundCap
|
||||
strokeStyle: edge != undefined && ((edge.src != undefined && edge.src.isOutput) || edge.dst == undefined) ? ShapePath.SolidLine : ShapePath.DashLine
|
||||
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 {
|
||||
id: cubic
|
||||
|
|
|
@ -236,10 +236,10 @@ Item {
|
|||
model: nodeRepeater.loaded && root.graph ? root.graph.edges : undefined
|
||||
|
||||
delegate: Edge {
|
||||
property var src: edge ? root._attributeToDelegate[edge.src] : undefined
|
||||
property var dst: edge ? root._attributeToDelegate[edge.dst] : undefined
|
||||
property var srcAnchor: src.nodeItem.mapFromItem(src, src.outputAnchorPos.x, src.outputAnchorPos.y)
|
||||
property var dstAnchor: dst.nodeItem.mapFromItem(dst, dst.inputAnchorPos.x, dst.inputAnchorPos.y)
|
||||
property var src: root._attributeToDelegate[edge.src]
|
||||
property var dst: root._attributeToDelegate[edge.dst]
|
||||
property bool isValidEdge: src != undefined && dst != undefined
|
||||
visible: isValidEdge
|
||||
|
||||
property bool inFocus: containsMouse || (edgeMenu.opened && edgeMenu.currentEdge == edge)
|
||||
|
||||
|
@ -247,10 +247,10 @@ Item {
|
|||
color: inFocus ? activePalette.highlight : activePalette.text
|
||||
thickness: inFocus ? 2 : 1
|
||||
opacity: 0.7
|
||||
point1x: src.nodeItem.x + srcAnchor.x
|
||||
point1y: src.nodeItem.y + srcAnchor.y
|
||||
point2x: dst.nodeItem.x + dstAnchor.x
|
||||
point2y: dst.nodeItem.y + dstAnchor.y
|
||||
point1x: isValidEdge ? src.globalX + src.outputAnchorPos.x : 0
|
||||
point1y: isValidEdge ? src.globalY + src.outputAnchorPos.y : 0
|
||||
point2x: isValidEdge ? dst.globalX + dst.inputAnchorPos.x : 0
|
||||
point2y: isValidEdge ? dst.globalY + dst.inputAnchorPos.y : 0
|
||||
onPressed: {
|
||||
const canEdit = !edge.dst.node.locked
|
||||
|
||||
|
|
|
@ -27,6 +27,11 @@ Item {
|
|||
readonly property color defaultColor: isCompatibilityNode ? "#444" : activePalette.base
|
||||
property color baseColor: defaultColor
|
||||
|
||||
Item {
|
||||
id: m
|
||||
property bool displayParams: false
|
||||
}
|
||||
|
||||
// Mouse interaction related signals
|
||||
signal pressed(var mouse)
|
||||
signal doubleClicked(var mouse)
|
||||
|
@ -60,7 +65,7 @@ Item {
|
|||
}
|
||||
|
||||
// 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
|
||||
// TODO: review this if we want to connect something else
|
||||
return attribute.type == "File"
|
||||
|
@ -110,7 +115,7 @@ Item {
|
|||
|
||||
// Selection border
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.fill: nodeContent
|
||||
anchors.margins: -border.width
|
||||
visible: root.selected || root.hovered
|
||||
border.width: 2.5
|
||||
|
@ -120,10 +125,9 @@ Item {
|
|||
color: "transparent"
|
||||
}
|
||||
|
||||
// Background
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.fill: nodeContent
|
||||
color: Qt.lighter(activePalette.base, 1.4)
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow { radius: 3; color: shadowColor }
|
||||
|
@ -131,192 +135,283 @@ Item {
|
|||
opacity: 0.7
|
||||
}
|
||||
|
||||
// Data Layout
|
||||
Column {
|
||||
id: body
|
||||
Rectangle {
|
||||
id: nodeContent
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
color: "transparent"
|
||||
|
||||
// Header
|
||||
Rectangle {
|
||||
id: header
|
||||
// Data Layout
|
||||
Column {
|
||||
id: body
|
||||
width: parent.width
|
||||
height: headerLayout.height
|
||||
color: root.selected ? activePalette.highlight : root.baseColor
|
||||
radius: background.radius
|
||||
|
||||
// Fill header's bottom radius
|
||||
// Header
|
||||
Rectangle {
|
||||
id: header
|
||||
width: parent.width
|
||||
height: parent.radius
|
||||
anchors.bottom: parent.bottom
|
||||
color: parent.color
|
||||
z: -1
|
||||
}
|
||||
height: headerLayout.height
|
||||
color: root.selected ? activePalette.highlight : root.baseColor
|
||||
radius: background.radius
|
||||
|
||||
// Header Layout
|
||||
RowLayout {
|
||||
id: headerLayout
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
// Node Name
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: node ? node.label : ""
|
||||
padding: 4
|
||||
color: root.selected ? "white" : activePalette.text
|
||||
elide: Text.ElideMiddle
|
||||
font.pointSize: 8
|
||||
// Fill header's bottom radius
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.radius
|
||||
anchors.bottom: parent.bottom
|
||||
color: parent.color
|
||||
z: -1
|
||||
}
|
||||
|
||||
// Node State icons
|
||||
// Header Layout
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: 2
|
||||
spacing: 2
|
||||
id: headerLayout
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
// CompatibilityBadge icon for CompatibilityNodes
|
||||
Loader {
|
||||
active: root.isCompatibilityNode
|
||||
sourceComponent: CompatibilityBadge {
|
||||
sourceComponent: iconDelegate
|
||||
canUpgrade: root.node.canUpgrade
|
||||
issueDetails: root.node.issueDetails
|
||||
// Node Name
|
||||
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
|
||||
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
|
||||
// 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
|
||||
// (sharing the same uid) will not be as slow. If save, quit and reload, it will become slow.
|
||||
MaterialToolButton {
|
||||
property string baseText: "<b>Shares internal folder (data) with other node(s). Hold click for details.</b>"
|
||||
property string toolTipText: visible ? baseText : ""
|
||||
visible: node.hasDuplicates
|
||||
text: MaterialIcons.layers
|
||||
font.pointSize: 7
|
||||
padding: 2
|
||||
palette.text: Colors.sysPalette.text
|
||||
ToolTip.text: toolTipText
|
||||
// Data sharing indicator
|
||||
// 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
|
||||
// (sharing the same uid) will not be as slow. If save, quit and reload, it will become slow.
|
||||
MaterialToolButton {
|
||||
property string baseText: "<b>Shares internal folder (data) with other node(s). Hold click for details.</b>"
|
||||
property string toolTipText: visible ? baseText : ""
|
||||
visible: node.hasDuplicates
|
||||
text: MaterialIcons.layers
|
||||
font.pointSize: 7
|
||||
padding: 2
|
||||
palette.text: Colors.sysPalette.text
|
||||
ToolTip.text: toolTipText
|
||||
|
||||
onPressed: { offsetReleased.running = false; toolTipText = visible ? generateDuplicateList() : "" }
|
||||
onReleased: { toolTipText = "" ; offsetReleased.running = true }
|
||||
onCanceled: released()
|
||||
onPressed: { offsetReleased.running = false; toolTipText = visible ? generateDuplicateList() : "" }
|
||||
onReleased: { toolTipText = "" ; offsetReleased.running = true }
|
||||
onCanceled: released()
|
||||
|
||||
// Used for a better user experience with the button
|
||||
// Avoid to change the text too quickly
|
||||
Timer {
|
||||
id: offsetReleased
|
||||
interval: 750; running: false; repeat: false
|
||||
onTriggered: parent.toolTipText = visible ? parent.baseText : ""
|
||||
// Used for a better user experience with the button
|
||||
// Avoid to change the text too quickly
|
||||
Timer {
|
||||
id: offsetReleased
|
||||
interval: 750; running: false; repeat: false
|
||||
onTriggered: parent.toolTipText = visible ? parent.baseText : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Submitted externally indicator
|
||||
MaterialLabel {
|
||||
visible: ["SUBMITTED", "RUNNING"].includes(node.globalStatus) && node.chunks.count > 0 && node.globalExecMode === "EXTERN"
|
||||
text: MaterialIcons.cloud
|
||||
padding: 2
|
||||
font.pointSize: 7
|
||||
palette.text: Colors.sysPalette.text
|
||||
ToolTip.text: "Computed Externally"
|
||||
}
|
||||
// Submitted externally indicator
|
||||
MaterialLabel {
|
||||
visible: ["SUBMITTED", "RUNNING"].includes(node.globalStatus) && node.chunks.count > 0 && node.globalExecMode === "EXTERN"
|
||||
text: MaterialIcons.cloud
|
||||
padding: 2
|
||||
font.pointSize: 7
|
||||
palette.text: Colors.sysPalette.text
|
||||
ToolTip.text: "Computed Externally"
|
||||
}
|
||||
|
||||
// Lock indicator
|
||||
MaterialLabel {
|
||||
visible: root.readOnly
|
||||
text: MaterialIcons.lock
|
||||
padding: 2
|
||||
font.pointSize: 7
|
||||
palette.text: "red"
|
||||
ToolTip.text: "Locked"
|
||||
// Lock indicator
|
||||
MaterialLabel {
|
||||
visible: root.readOnly
|
||||
text: MaterialIcons.lock
|
||||
padding: 2
|
||||
font.pointSize: 7
|
||||
palette.text: "red"
|
||||
ToolTip.text: "Locked"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Node Chunks
|
||||
NodeChunks {
|
||||
defaultColor: Colors.sysPalette.mid
|
||||
implicitHeight: 3
|
||||
width: parent.width
|
||||
model: node ? node.chunks : undefined
|
||||
// Node Chunks
|
||||
NodeChunks {
|
||||
defaultColor: Colors.sysPalette.mid
|
||||
implicitHeight: 3
|
||||
width: parent.width
|
||||
model: node ? node.chunks : undefined
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.sysPalette.mid
|
||||
z: -1
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.sysPalette.mid
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical Spacer
|
||||
Item { width: parent.width; height: 2 }
|
||||
// Vertical Spacer
|
||||
Item { width: parent.width; height: 2 }
|
||||
|
||||
// Input/Output Attributes
|
||||
Item {
|
||||
id: nodeAttributes
|
||||
width: parent.width - 2
|
||||
height: childrenRect.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
// Input/Output Attributes
|
||||
Item {
|
||||
id: nodeAttributes
|
||||
width: parent.width - 2
|
||||
height: childrenRect.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
enabled: !root.readOnly && !root.isCompatibilityNode
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
bottomPadding: 2
|
||||
enabled: !root.isCompatibilityNode
|
||||
|
||||
Column {
|
||||
id: outputs
|
||||
id: attributesColumn
|
||||
width: parent.width
|
||||
spacing: 3
|
||||
Repeater {
|
||||
model: node ? node.attributes : undefined
|
||||
spacing: 5
|
||||
bottomPadding: 2
|
||||
|
||||
delegate: Loader {
|
||||
id: outputLoader
|
||||
active: object.isOutput && isDisplayableAsPin(object)
|
||||
anchors.right: parent.right
|
||||
width: outputs.width
|
||||
Column {
|
||||
id: outputs
|
||||
width: parent.width
|
||||
spacing: 3
|
||||
Repeater {
|
||||
model: node ? node.attributes : undefined
|
||||
|
||||
sourceComponent: AttributePin {
|
||||
id: outPin
|
||||
nodeItem: root
|
||||
attribute: object
|
||||
delegate: Loader {
|
||||
id: outputLoader
|
||||
active: object.isOutput && isFileAttributeBaseType(object)
|
||||
anchors.right: parent.right
|
||||
width: outputs.width
|
||||
|
||||
readOnly: root.readOnly
|
||||
onPressed: root.pressed(mouse)
|
||||
Component.onCompleted: attributePinCreated(object, outPin)
|
||||
Component.onDestruction: attributePinDeleted(attribute, outPin)
|
||||
sourceComponent: AttributePin {
|
||||
id: outPin
|
||||
nodeItem: root
|
||||
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 {
|
||||
id: inputs
|
||||
width: parent.width
|
||||
spacing: 3
|
||||
Repeater {
|
||||
model: node ? node.attributes : undefined
|
||||
delegate: Loader {
|
||||
active: !object.isOutput && isDisplayableAsPin(object)
|
||||
width: inputs.width
|
||||
Column {
|
||||
id: inputs
|
||||
width: parent.width
|
||||
spacing: 3
|
||||
Repeater {
|
||||
model: node ? node.attributes : undefined
|
||||
delegate: Loader {
|
||||
id: inputLoader
|
||||
active: !object.isOutput && isFileAttributeBaseType(object)
|
||||
width: inputs.width
|
||||
|
||||
sourceComponent: AttributePin {
|
||||
id: inPin
|
||||
nodeItem: root
|
||||
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)
|
||||
sourceComponent: AttributePin {
|
||||
id: inPin
|
||||
nodeItem: root
|
||||
attribute: object
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,7 +129,52 @@ FloatingPane {
|
|||
id: searchBar
|
||||
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
|
||||
ListView {
|
||||
id: metadataView
|
||||
|
|
|
@ -123,6 +123,8 @@ FocusScope {
|
|||
}
|
||||
|
||||
function getImageFile(type) {
|
||||
if(!_reconstruction.activeNodes)
|
||||
return "";
|
||||
var depthMapNode = _reconstruction.activeNodes.get('allDepthMap').node;
|
||||
if (type == "image") {
|
||||
return root.source;
|
||||
|
@ -240,8 +242,7 @@ FocusScope {
|
|||
}
|
||||
|
||||
// Image cache of the last loaded image
|
||||
// Only visible when the main one is loading, to keep an image
|
||||
// displayed at all time and smoothen transitions
|
||||
// Only visible when the main one is loading, to maintain a displayed image for smoother transitions
|
||||
Image {
|
||||
id: qtImageViewerCache
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue