Skip to content

Commit 1624c9a

Browse files
[core/ui] Graph: The graph now use a DAGVisitor to avoid cyclic dependency connection
1 parent c9f05d7 commit 1624c9a

File tree

3 files changed

+29
-10
lines changed

3 files changed

+29
-10
lines changed

meshroom/core/exception.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,8 @@ class StopGraphVisit(GraphVisitMessage):
6464
class StopBranchVisit(GraphVisitMessage):
6565
""" Immediately stop branch visit. """
6666
pass
67+
68+
69+
class CyclicDependencyError(Exception):
70+
""" Raised if a cyclic dependency is find in a DAG graph """
71+
pass

meshroom/core/graph.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from meshroom.common import BaseObject, DictModel, Slot, Signal, Property
1616
from meshroom.core import Version
1717
from meshroom.core.attribute import Attribute, ListAttribute, GroupAttribute
18-
from meshroom.core.exception import GraphCompatibilityError, InvalidEdgeError, StopGraphVisit, StopBranchVisit
18+
from meshroom.core.exception import GraphCompatibilityError, InvalidEdgeError, StopGraphVisit, StopBranchVisit, CyclicDependencyError
1919
from meshroom.core.graphIO import GraphIO, GraphSerializer, TemplateGraphSerializer, PartialGraphSerializer
2020
from meshroom.core.node import BaseNode, Status, Node, CompatibilityNode
2121
from meshroom.core.nodeFactory import nodeFactory
@@ -134,6 +134,14 @@ def finishVertex(self, u, g):
134134
pass
135135

136136

137+
class DAGVisitor(Visitor):
138+
139+
def backEdge(self, e, g):
140+
""" Is invoked on the back edges in the graph. Means that there is a cyclic dependency in the visited graph """
141+
142+
raise CyclicDependencyError("A cyclic dependency exists on the current DAG")
143+
144+
137145
def changeTopology(func):
138146
"""
139147
Graph methods modifying the graph topology (add/remove edges or nodes)
@@ -889,7 +897,7 @@ def getRootNodes(self, dependenciesOnly):
889897
return set(self._nodes) - nodesWithInputLink
890898

891899
@changeTopology
892-
def addEdge(self, srcAttr: Attribute, dstAttr: Attribute):
900+
def addEdge(self, srcAttr: Attribute, dstAttr: Attribute) -> "Edge":
893901
if not (srcAttr.node.graph == dstAttr.node.graph == self):
894902
raise InvalidEdgeError(
895903
srcAttr.fullNameToGraph, dstAttr.fullNameToGraph, "Attributes do not belong to this Graph"
@@ -1030,7 +1038,7 @@ def dfsOnFinish(self, startNodes=None, longestPathFirst=False, reverse=False, de
10301038
"""
10311039
nodes = []
10321040
edges = []
1033-
visitor = Visitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
1041+
visitor = DAGVisitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
10341042
visitor.finishVertex = lambda vertex, graph: nodes.append(vertex)
10351043
visitor.finishEdge = lambda edge, graph: edges.append(edge)
10361044
self.dfs(visitor=visitor, startNodes=startNodes, longestPathFirst=longestPathFirst)
@@ -1055,7 +1063,7 @@ def dfsOnDiscover(self, startNodes=None, filterTypes=None, longestPathFirst=Fals
10551063
"""
10561064
nodes = []
10571065
edges = []
1058-
visitor = Visitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
1066+
visitor = DAGVisitor(reverse=reverse, dependenciesOnly=dependenciesOnly)
10591067

10601068
def discoverVertex(vertex, graph):
10611069
if not filterTypes or vertex.nodeType in filterTypes:
@@ -1079,7 +1087,7 @@ def dfsToProcess(self, startNodes=None):
10791087
"""
10801088
nodes = []
10811089
edges = []
1082-
visitor = Visitor(reverse=False, dependenciesOnly=True)
1090+
visitor = DAGVisitor(reverse=False, dependenciesOnly=True)
10831091

10841092
def discoverVertex(vertex, graph):
10851093
if vertex.hasStatus(Status.SUCCESS):
@@ -1135,7 +1143,7 @@ def updateNodesTopologicalData(self):
11351143
self._computationBlocked.clear()
11361144

11371145
compatNodes = []
1138-
visitor = Visitor(reverse=False, dependenciesOnly=False)
1146+
visitor = DAGVisitor(reverse=False, dependenciesOnly=False)
11391147

11401148
def discoverVertex(vertex, graph):
11411149
# initialize depths
@@ -1193,7 +1201,7 @@ def dfsMaxEdgeLength(self, startNodes=None, dependenciesOnly=True):
11931201
"""
11941202
nodesStack = []
11951203
edgesScore = defaultdict(int)
1196-
visitor = Visitor(reverse=False, dependenciesOnly=dependenciesOnly)
1204+
visitor = DAGVisitor(reverse=False, dependenciesOnly=dependenciesOnly)
11971205

11981206
def finishEdge(edge, graph):
11991207
u, v = edge
@@ -1276,7 +1284,7 @@ def canSubmitOrCompute(self, startNode):
12761284
if startNode.isAlreadySubmittedOrFinished():
12771285
return 0
12781286

1279-
class SCVisitor(Visitor):
1287+
class SCVisitor(DAGVisitor):
12801288
def __init__(self, reverse, dependenciesOnly):
12811289
super().__init__(reverse, dependenciesOnly)
12821290

meshroom/ui/commands.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from PySide6.QtCore import Property, Signal
77

88
from meshroom.core.attribute import ListAttribute, Attribute
9-
from meshroom.core.graph import Graph, GraphModification
9+
from meshroom.core.graph import Graph, GraphModification, CyclicDependencyError
1010
from meshroom.core.node import Position, CompatibilityIssue
1111
from meshroom.core.nodeFactory import nodeFactory
1212
from meshroom.core.mtyping import PathLike
@@ -318,7 +318,13 @@ def __init__(self, graph, src, dst, parent=None):
318318
raise ValueError(f"Attribute are not compatible and cannot be connected: '{self.srcAttr}'({src.baseType})->'{self.dstAttr}'({dst.baseType})")
319319

320320
def redoImpl(self):
321-
self.graph.addEdge(self.graph.attribute(self.srcAttr), self.graph.attribute(self.dstAttr))
321+
322+
try:
323+
self.graph.addEdge(self.graph.attribute(self.srcAttr), self.graph.attribute(self.dstAttr))
324+
except CyclicDependencyError:
325+
self.graph.removeEdge(self.graph.attribute(self.dstAttr))
326+
return False
327+
322328
return True
323329

324330
def undoImpl(self):

0 commit comments

Comments
 (0)