mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-22 21:46:28 +02:00
[graph] automatically trigger Graph updates
* update graph internals / node status when: - an input attribute is set - edges are added/removed * add GraphModification context manager to group graph updates
This commit is contained in:
parent
a0cdc65143
commit
5e4b26fa5e
1 changed files with 67 additions and 18 deletions
|
@ -11,6 +11,7 @@ import shutil
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from contextlib import contextmanager
|
||||||
from enum import Enum # available by default in python3. For python2: "pip install enum34"
|
from enum import Enum # available by default in python3. For python2: "pip install enum34"
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -48,6 +49,29 @@ else:
|
||||||
basestring = basestring
|
basestring = basestring
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def GraphModification(graph):
|
||||||
|
"""
|
||||||
|
A Context Manager that can be used to trigger only one Graph update
|
||||||
|
for a group of several modifications.
|
||||||
|
GraphModifications can be nested.
|
||||||
|
"""
|
||||||
|
if not isinstance(graph, Graph):
|
||||||
|
raise ValueError("GraphModification expects a Graph instance")
|
||||||
|
# Store update policy
|
||||||
|
enabled = graph.updateEnabled
|
||||||
|
# Disable graph update for nested block
|
||||||
|
# (does nothing if already disabled)
|
||||||
|
graph.updateEnabled = False
|
||||||
|
try:
|
||||||
|
yield # Execute nested block
|
||||||
|
except Exception:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
# Restore update policy
|
||||||
|
graph.updateEnabled = enabled
|
||||||
|
|
||||||
|
|
||||||
def hash(v):
|
def hash(v):
|
||||||
hashObject = hashlib.sha1(str(v).encode('utf-8'))
|
hashObject = hashlib.sha1(str(v).encode('utf-8'))
|
||||||
return hashObject.hexdigest()
|
return hashObject.hexdigest()
|
||||||
|
@ -120,6 +144,14 @@ class Attribute(BaseObject):
|
||||||
if self._value == value:
|
if self._value == value:
|
||||||
return
|
return
|
||||||
self._value = value
|
self._value = value
|
||||||
|
# Request graph update when input parameter value is set
|
||||||
|
# and parent node belongs to a graph
|
||||||
|
# Output attributes value are set internally during the update process,
|
||||||
|
# which is why we don't trigger any update in this case
|
||||||
|
# TODO: update only the nodes impacted by this change
|
||||||
|
# TODO: only update the graph if this attribute participates to a UID
|
||||||
|
if self.node.graph and self.attributeDesc.isInput:
|
||||||
|
self.node.graph.update()
|
||||||
self.valueChanged.emit()
|
self.valueChanged.emit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -409,8 +441,9 @@ class Node(BaseObject):
|
||||||
self.attributesPerUid[uidIndex].add(attr)
|
self.attributesPerUid[uidIndex].add(attr)
|
||||||
|
|
||||||
def _applyExpr(self):
|
def _applyExpr(self):
|
||||||
for attr in self._attributes:
|
with GraphModification(self.graph):
|
||||||
attr._applyExpr()
|
for attr in self._attributes:
|
||||||
|
attr._applyExpr()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def nodeType(self):
|
def nodeType(self):
|
||||||
|
@ -690,6 +723,8 @@ class Graph(BaseObject):
|
||||||
def __init__(self, name, parent=None):
|
def __init__(self, name, parent=None):
|
||||||
super(Graph, self).__init__(parent)
|
super(Graph, self).__init__(parent)
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self._updateEnabled = True
|
||||||
|
self._updateRequested = False
|
||||||
self._nodes = DictModel(keyAttrName='name', parent=self)
|
self._nodes = DictModel(keyAttrName='name', parent=self)
|
||||||
self._edges = DictModel(keyAttrName='dst', parent=self) # use dst attribute as unique key since it can only have one input connection
|
self._edges = DictModel(keyAttrName='dst', parent=self) # use dst attribute as unique key since it can only have one input connection
|
||||||
self._cacheDir = ''
|
self._cacheDir = ''
|
||||||
|
@ -718,6 +753,18 @@ class Graph(BaseObject):
|
||||||
# Create graph edges by resolving attributes expressions
|
# Create graph edges by resolving attributes expressions
|
||||||
self._applyExpr()
|
self._applyExpr()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def updateEnabled(self):
|
||||||
|
return self._updateEnabled
|
||||||
|
|
||||||
|
@updateEnabled.setter
|
||||||
|
def updateEnabled(self, enabled):
|
||||||
|
self._updateEnabled = enabled
|
||||||
|
if enabled and self._updateRequested:
|
||||||
|
# Trigger an update if requested while disabled
|
||||||
|
self.update()
|
||||||
|
self._updateRequested = False
|
||||||
|
|
||||||
def _addNode(self, node, uniqueName):
|
def _addNode(self, node, uniqueName):
|
||||||
"""
|
"""
|
||||||
Internal method to add the given node to this Graph, with the given name (must be unique).
|
Internal method to add the given node to this Graph, with the given name (must be unique).
|
||||||
|
@ -734,10 +781,6 @@ class Graph(BaseObject):
|
||||||
self._nodes.add(node)
|
self._nodes.add(node)
|
||||||
self.stopExecutionRequested.connect(node.stopProcess)
|
self.stopExecutionRequested.connect(node.stopProcess)
|
||||||
|
|
||||||
# Trigger internal update when an attribute is modified
|
|
||||||
for attr in node.attributes: # type: Attribute
|
|
||||||
attr.valueChanged.connect(self.updateInternals)
|
|
||||||
|
|
||||||
def addNode(self, node, uniqueName=None):
|
def addNode(self, node, uniqueName=None):
|
||||||
"""
|
"""
|
||||||
Add the given node to this Graph with an optional unique name,
|
Add the given node to this Graph with an optional unique name,
|
||||||
|
@ -763,15 +806,15 @@ class Graph(BaseObject):
|
||||||
|
|
||||||
inEdges = {}
|
inEdges = {}
|
||||||
outEdges = {}
|
outEdges = {}
|
||||||
for attr in node._attributes:
|
with GraphModification(self):
|
||||||
for edge in self.outEdges(attr):
|
for attr in node._attributes:
|
||||||
self.edges.remove(edge)
|
for edge in self.outEdges(attr):
|
||||||
outEdges[edge.dst.fullName()] = edge.src.fullName()
|
self.edges.remove(edge)
|
||||||
if attr in self.edges.keys():
|
outEdges[edge.dst.fullName()] = edge.src.fullName()
|
||||||
edge = self.edges.pop(attr)
|
if attr in self.edges.keys():
|
||||||
inEdges[edge.dst.fullName()] = edge.src.fullName()
|
edge = self.edges.pop(attr)
|
||||||
|
inEdges[edge.dst.fullName()] = edge.src.fullName()
|
||||||
|
|
||||||
self.updateInternals()
|
|
||||||
return inEdges, outEdges
|
return inEdges, outEdges
|
||||||
|
|
||||||
@Slot(str, result=Node)
|
@Slot(str, result=Node)
|
||||||
|
@ -838,11 +881,13 @@ class Graph(BaseObject):
|
||||||
self.edges.add(edge)
|
self.edges.add(edge)
|
||||||
dstAttr.valueChanged.emit()
|
dstAttr.valueChanged.emit()
|
||||||
dstAttr.isLinkChanged.emit()
|
dstAttr.isLinkChanged.emit()
|
||||||
|
self.update()
|
||||||
return edge
|
return edge
|
||||||
|
|
||||||
def addEdges(self, *edges):
|
def addEdges(self, *edges):
|
||||||
for edge in edges:
|
with GraphModification(self):
|
||||||
self.addEdge(*edge)
|
for edge in edges:
|
||||||
|
self.addEdge(*edge)
|
||||||
|
|
||||||
def removeEdge(self, dstAttr):
|
def removeEdge(self, dstAttr):
|
||||||
if dstAttr not in self.edges.keys():
|
if dstAttr not in self.edges.keys():
|
||||||
|
@ -850,6 +895,7 @@ class Graph(BaseObject):
|
||||||
edge = self.edges.pop(dstAttr)
|
edge = self.edges.pop(dstAttr)
|
||||||
dstAttr.valueChanged.emit()
|
dstAttr.valueChanged.emit()
|
||||||
dstAttr.isLinkChanged.emit()
|
dstAttr.isLinkChanged.emit()
|
||||||
|
self.update()
|
||||||
return edge
|
return edge
|
||||||
|
|
||||||
def getDepth(self, node):
|
def getDepth(self, node):
|
||||||
|
@ -1028,8 +1074,9 @@ class Graph(BaseObject):
|
||||||
return flowEdges
|
return flowEdges
|
||||||
|
|
||||||
def _applyExpr(self):
|
def _applyExpr(self):
|
||||||
for node in self._nodes:
|
with GraphModification(self):
|
||||||
node._applyExpr()
|
for node in self._nodes:
|
||||||
|
node._applyExpr()
|
||||||
|
|
||||||
def toDict(self):
|
def toDict(self):
|
||||||
return {k: node.toDict() for k, node in self._nodes.objects.items()}
|
return {k: node.toDict() for k, node in self._nodes.objects.items()}
|
||||||
|
@ -1060,6 +1107,8 @@ class Graph(BaseObject):
|
||||||
node.updateStatisticsFromCache()
|
node.updateStatisticsFromCache()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
if not self._updateEnabled:
|
||||||
|
self._updateRequested = True
|
||||||
self.updateInternals()
|
self.updateInternals()
|
||||||
self.updateStatusFromCache()
|
self.updateStatusFromCache()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue