mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-22 05:26:29 +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):
|
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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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.baseType != inputDragTarget.baseType // not the same base type
|
||||||
|| drag.source.nodeItem == inputDragTarget.nodeItem // connection between attributes of the same node
|
|| drag.source.nodeItem == inputDragTarget.nodeItem // connection between attributes of the same node
|
||||||
|| inputDragTarget.attribute.isLink // already connected attribute
|
|| inputDragTarget.attribute.isLink // already connected attribute
|
||||||
|| (drag.source.isList && !inputDragTarget.isList) // connection between a list and a simple 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.isList && childrenRepeater.count) // source/target are lists but target already has children
|
||||||
|| drag.source.connectorType == "input"
|
|| 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.baseType != outputDragTarget.baseType // not the same base type
|
||||||
|| drag.source.nodeItem == outputDragTarget.nodeItem // connection between attributes of the same node
|
|| drag.source.nodeItem == outputDragTarget.nodeItem // connection between attributes of the same node
|
||||||
|| drag.source.attribute.isLink // already connected attribute
|
|| drag.source.attribute.isLink // already connected attribute
|
||||||
|| (!drag.source.isList && outputDragTarget.isList) // connection between a list and a simple 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.isList && childrenRepeater.count) // source/target are lists but target already has children
|
||||||
|| drag.source.connectorType == "output"
|
|| 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,6 +135,12 @@ Item {
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: nodeContent
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
// Data Layout
|
// Data Layout
|
||||||
Column {
|
Column {
|
||||||
id: body
|
id: body
|
||||||
|
@ -260,9 +270,10 @@ Item {
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
enabled: !root.readOnly && !root.isCompatibilityNode
|
enabled: !root.isCompatibilityNode
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
id: attributesColumn
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 5
|
spacing: 5
|
||||||
bottomPadding: 2
|
bottomPadding: 2
|
||||||
|
@ -276,7 +287,7 @@ Item {
|
||||||
|
|
||||||
delegate: Loader {
|
delegate: Loader {
|
||||||
id: outputLoader
|
id: outputLoader
|
||||||
active: object.isOutput && isDisplayableAsPin(object)
|
active: object.isOutput && isFileAttributeBaseType(object)
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
width: outputs.width
|
width: outputs.width
|
||||||
|
|
||||||
|
@ -285,7 +296,9 @@ Item {
|
||||||
nodeItem: root
|
nodeItem: root
|
||||||
attribute: object
|
attribute: object
|
||||||
|
|
||||||
readOnly: root.readOnly
|
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)
|
onPressed: root.pressed(mouse)
|
||||||
Component.onCompleted: attributePinCreated(object, outPin)
|
Component.onCompleted: attributePinCreated(object, outPin)
|
||||||
Component.onDestruction: attributePinDeleted(attribute, outPin)
|
Component.onDestruction: attributePinDeleted(attribute, outPin)
|
||||||
|
@ -301,13 +314,79 @@ Item {
|
||||||
Repeater {
|
Repeater {
|
||||||
model: node ? node.attributes : undefined
|
model: node ? node.attributes : undefined
|
||||||
delegate: Loader {
|
delegate: Loader {
|
||||||
active: !object.isOutput && isDisplayableAsPin(object)
|
id: inputLoader
|
||||||
|
active: !object.isOutput && isFileAttributeBaseType(object)
|
||||||
width: inputs.width
|
width: inputs.width
|
||||||
|
|
||||||
sourceComponent: AttributePin {
|
sourceComponent: AttributePin {
|
||||||
id: inPin
|
id: inPin
|
||||||
nodeItem: root
|
nodeItem: root
|
||||||
attribute: object
|
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
|
readOnly: root.readOnly
|
||||||
Component.onCompleted: attributePinCreated(attribute, inPin)
|
Component.onCompleted: attributePinCreated(attribute, inPin)
|
||||||
Component.onDestruction: attributePinDeleted(attribute, inPin)
|
Component.onDestruction: attributePinDeleted(attribute, inPin)
|
||||||
|
@ -319,6 +398,22 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue