mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-22 21:46:28 +02:00
Merge branch 'dev_processGraph' of https://github.com/alicevision/meshroom into dev_processGraph
Conflicts: meshroom/core/graph.py
This commit is contained in:
commit
fd1b9f2cea
8 changed files with 381 additions and 43 deletions
|
@ -99,12 +99,13 @@ class Attribute(BaseObject):
|
||||||
# only dependent of the linked node uid, so it is independent
|
# only dependent of the linked node uid, so it is independent
|
||||||
# from the cache folder which may be used in the filepath.
|
# from the cache folder which may be used in the filepath.
|
||||||
return self.node.uid()
|
return self.node.uid()
|
||||||
if self.isLink():
|
if self.isLink:
|
||||||
return self.getLinkParam().uid()
|
return self.getLinkParam().uid()
|
||||||
if isinstance(self._value, basestring):
|
if isinstance(self._value, basestring):
|
||||||
return hash(str(self._value))
|
return hash(str(self._value))
|
||||||
return hash(self._value)
|
return hash(self._value)
|
||||||
|
|
||||||
|
@property
|
||||||
def isLink(self):
|
def isLink(self):
|
||||||
"""
|
"""
|
||||||
If the attribute is a link to another attribute.
|
If the attribute is a link to another attribute.
|
||||||
|
@ -115,7 +116,7 @@ class Attribute(BaseObject):
|
||||||
return self in self.node.graph.edges.keys()
|
return self in self.node.graph.edges.keys()
|
||||||
|
|
||||||
def getLinkParam(self):
|
def getLinkParam(self):
|
||||||
if not self.isLink():
|
if not self.isLink:
|
||||||
return None
|
return None
|
||||||
return self.node.graph.edge(self).src
|
return self.node.graph.edge(self).src
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ class Attribute(BaseObject):
|
||||||
def getExportValue(self):
|
def getExportValue(self):
|
||||||
value = self._value
|
value = self._value
|
||||||
# print('getExportValue: ', self.name(), value, self.isLink())
|
# print('getExportValue: ', self.name(), value, self.isLink())
|
||||||
if self.isLink():
|
if self.isLink:
|
||||||
value = '{' + self.getLinkParam().fullName() + '}'
|
value = '{' + self.getLinkParam().fullName() + '}'
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -150,6 +151,8 @@ class Attribute(BaseObject):
|
||||||
valueChanged = Signal()
|
valueChanged = Signal()
|
||||||
value = Property("QVariant", value.fget, value.fset, notify=valueChanged)
|
value = Property("QVariant", value.fget, value.fset, notify=valueChanged)
|
||||||
isOutput = Property(bool, isOutput.fget, constant=True)
|
isOutput = Property(bool, isOutput.fget, constant=True)
|
||||||
|
isLinkChanged = Signal()
|
||||||
|
isLink = Property(bool, isLink.fget, notify=isLinkChanged)
|
||||||
|
|
||||||
|
|
||||||
class Edge(BaseObject):
|
class Edge(BaseObject):
|
||||||
|
@ -223,6 +226,7 @@ class Node(BaseObject):
|
||||||
self.attribute(k)._value = v
|
self.attribute(k)._value = v
|
||||||
self.status = StatusData(self.name, self.nodeType())
|
self.status = StatusData(self.name, self.nodeType())
|
||||||
self.statistics = stats.Statistics()
|
self.statistics = stats.Statistics()
|
||||||
|
self._subprocess = None
|
||||||
|
|
||||||
def __getattr__(self, k):
|
def __getattr__(self, k):
|
||||||
try:
|
try:
|
||||||
|
@ -354,11 +358,12 @@ class Node(BaseObject):
|
||||||
"""
|
"""
|
||||||
statusFile = self.statusFile()
|
statusFile = self.statusFile()
|
||||||
if not os.path.exists(statusFile):
|
if not os.path.exists(statusFile):
|
||||||
self.status.status = Status.NONE
|
self.upgradeStatusTo(Status.NONE)
|
||||||
return
|
return
|
||||||
with open(statusFile, 'r') as jsonFile:
|
with open(statusFile, 'r') as jsonFile:
|
||||||
statusData = json.load(jsonFile)
|
statusData = json.load(jsonFile)
|
||||||
self.status.fromDict(statusData)
|
self.status.fromDict(statusData)
|
||||||
|
self.statusChanged.emit()
|
||||||
|
|
||||||
def saveStatusFile(self):
|
def saveStatusFile(self):
|
||||||
"""
|
"""
|
||||||
|
@ -400,6 +405,7 @@ class Node(BaseObject):
|
||||||
print('WARNING: downgrade status on node "{}" from {} to {}'.format(self._name, self.status.status.name,
|
print('WARNING: downgrade status on node "{}" from {} to {}'.format(self._name, self.status.status.name,
|
||||||
newStatus))
|
newStatus))
|
||||||
self.status.status = newStatus
|
self.status.status = newStatus
|
||||||
|
self.statusChanged.emit()
|
||||||
self.saveStatusFile()
|
self.saveStatusFile()
|
||||||
|
|
||||||
def isAlreadySubmitted(self):
|
def isAlreadySubmitted(self):
|
||||||
|
@ -411,6 +417,10 @@ class Node(BaseObject):
|
||||||
def beginSequence(self):
|
def beginSequence(self):
|
||||||
self.upgradeStatusTo(Status.SUBMITTED_LOCAL)
|
self.upgradeStatusTo(Status.SUBMITTED_LOCAL)
|
||||||
|
|
||||||
|
def stopProcess(self):
|
||||||
|
if self._subprocess:
|
||||||
|
self._subprocess.terminate()
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
self.upgradeStatusTo(Status.RUNNING)
|
self.upgradeStatusTo(Status.RUNNING)
|
||||||
statThread = stats.StatisticsThread(self)
|
statThread = stats.StatisticsThread(self)
|
||||||
|
@ -421,20 +431,20 @@ class Node(BaseObject):
|
||||||
cmd = self.commandLine()
|
cmd = self.commandLine()
|
||||||
print(' - commandLine:', cmd)
|
print(' - commandLine:', cmd)
|
||||||
print(' - logFile:', self.logFile())
|
print(' - logFile:', self.logFile())
|
||||||
self.proc = psutil.Popen(cmd, stdout=logF, stderr=logF, shell=True)
|
self._subprocess = psutil.Popen(cmd, stdout=logF, stderr=logF, shell=True)
|
||||||
|
|
||||||
# store process static info into the status file
|
# store process static info into the status file
|
||||||
self.status.commandLine = cmd
|
self.status.commandLine = cmd
|
||||||
# self.status.env = self.proc.environ()
|
# self.status.env = self.proc.environ()
|
||||||
# self.status.createTime = self.proc.create_time()
|
# self.status.createTime = self.proc.create_time()
|
||||||
|
|
||||||
statThread.proc = self.proc
|
statThread.proc = self._subprocess
|
||||||
stdout, stderr = self.proc.communicate()
|
stdout, stderr = self._subprocess.communicate()
|
||||||
self.proc.wait()
|
self._subprocess.wait()
|
||||||
|
|
||||||
self.status.returnCode = self.proc.returncode
|
self.status.returnCode = self._subprocess.returncode
|
||||||
|
|
||||||
if self.proc.returncode != 0:
|
if self._subprocess.returncode != 0:
|
||||||
logContent = ''
|
logContent = ''
|
||||||
with open(self.logFile(), 'r') as logF:
|
with open(self.logFile(), 'r') as logF:
|
||||||
logContent = ''.join(logF.readlines())
|
logContent = ''.join(logF.readlines())
|
||||||
|
@ -443,11 +453,13 @@ class Node(BaseObject):
|
||||||
except:
|
except:
|
||||||
self.upgradeStatusTo(Status.ERROR)
|
self.upgradeStatusTo(Status.ERROR)
|
||||||
raise
|
raise
|
||||||
elapsedTime = time.time() - startTime
|
finally:
|
||||||
print(' - elapsed time:', elapsedTime)
|
elapsedTime = time.time() - startTime
|
||||||
statThread.running = False
|
print(' - elapsed time:', elapsedTime)
|
||||||
# Don't need to join, the thread will finish a bit later.
|
self._subprocess = None
|
||||||
# statThread.join()
|
# ask and wait for the stats thread to terminate
|
||||||
|
statThread.stopRequest()
|
||||||
|
statThread.join()
|
||||||
|
|
||||||
self.upgradeStatusTo(Status.SUCCESS)
|
self.upgradeStatusTo(Status.SUCCESS)
|
||||||
|
|
||||||
|
@ -457,12 +469,19 @@ class Node(BaseObject):
|
||||||
def getStatus(self):
|
def getStatus(self):
|
||||||
return self.status
|
return self.status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def statusName(self):
|
||||||
|
return self.status.status.name
|
||||||
|
|
||||||
name = Property(str, getName, constant=True)
|
name = Property(str, getName, constant=True)
|
||||||
attributes = Property(BaseObject, getAttributes, constant=True)
|
attributes = Property(BaseObject, getAttributes, constant=True)
|
||||||
internalFolderChanged = Signal()
|
internalFolderChanged = Signal()
|
||||||
internalFolder = Property(str, internalFolder.fget, notify=internalFolderChanged)
|
internalFolder = Property(str, internalFolder.fget, notify=internalFolderChanged)
|
||||||
depthChanged = Signal()
|
depthChanged = Signal()
|
||||||
depth = Property(int, depth.fget, notify=depthChanged)
|
depth = Property(int, depth.fget, notify=depthChanged)
|
||||||
|
statusChanged = Signal()
|
||||||
|
statusName = Property(str, statusName.fget, notify=statusChanged)
|
||||||
|
|
||||||
|
|
||||||
WHITE = 0
|
WHITE = 0
|
||||||
GRAY = 1
|
GRAY = 1
|
||||||
|
@ -555,6 +574,7 @@ class Graph(BaseObject):
|
||||||
node._name = self._createUniqueNodeName(node.nodeType())
|
node._name = self._createUniqueNodeName(node.nodeType())
|
||||||
node.graph = self
|
node.graph = self
|
||||||
self._nodes.add(node)
|
self._nodes.add(node)
|
||||||
|
self.stopExecutionRequested.connect(node.stopProcess)
|
||||||
|
|
||||||
# Trigger internal update when an attribute is modified
|
# Trigger internal update when an attribute is modified
|
||||||
for attr in node.attributes: # type: Attribute
|
for attr in node.attributes: # type: Attribute
|
||||||
|
@ -629,20 +649,31 @@ class Graph(BaseObject):
|
||||||
nodesWithOutput = set([edge.src.node for edge in self.edges])
|
nodesWithOutput = set([edge.src.node for edge in self.edges])
|
||||||
return set(self._nodes) - nodesWithOutput
|
return set(self._nodes) - nodesWithOutput
|
||||||
|
|
||||||
def addEdge(self, outputAttr, inputAttr):
|
def addEdge(self, srcAttr, dstAttr):
|
||||||
assert isinstance(outputAttr, Attribute)
|
assert isinstance(srcAttr, Attribute)
|
||||||
assert isinstance(inputAttr, Attribute)
|
assert isinstance(dstAttr, Attribute)
|
||||||
if outputAttr.node.graph != self or inputAttr.node.graph != self:
|
if srcAttr.node.graph != self or dstAttr.node.graph != self:
|
||||||
raise RuntimeError('The attributes of the edge should be part of a common graph.')
|
raise RuntimeError('The attributes of the edge should be part of a common graph.')
|
||||||
if inputAttr in self.edges.keys():
|
if dstAttr in self.edges.keys():
|
||||||
raise RuntimeError('Input attribute "{}" is already connected.'.format(inputAttr.fullName()))
|
raise RuntimeError('Destination attribute "{}" is already connected.'.format(dstAttr.fullName()))
|
||||||
self.edges.add(Edge(outputAttr, inputAttr))
|
edge = Edge(srcAttr, dstAttr)
|
||||||
inputAttr.valueChanged.emit()
|
self.edges.add(edge)
|
||||||
|
dstAttr.valueChanged.emit()
|
||||||
|
dstAttr.isLinkChanged.emit()
|
||||||
|
return edge
|
||||||
|
|
||||||
def addEdges(self, *edges):
|
def addEdges(self, *edges):
|
||||||
for edge in edges:
|
for edge in edges:
|
||||||
self.addEdge(*edge)
|
self.addEdge(*edge)
|
||||||
|
|
||||||
|
def removeEdge(self, dstAttr):
|
||||||
|
if dstAttr not in self.edges.keys():
|
||||||
|
raise RuntimeError('Attribute "{}" is not connected'.format(dstAttr.fullName()))
|
||||||
|
edge = self.edges.pop(dstAttr)
|
||||||
|
dstAttr.valueChanged.emit()
|
||||||
|
dstAttr.isLinkChanged.emit()
|
||||||
|
return edge
|
||||||
|
|
||||||
def getDepth(self, node):
|
def getDepth(self, node):
|
||||||
# TODO: would be better to use bfs instead of recursive function
|
# TODO: would be better to use bfs instead of recursive function
|
||||||
inputEdges = self.getInputEdges(node)
|
inputEdges = self.getInputEdges(node)
|
||||||
|
@ -753,6 +784,10 @@ class Graph(BaseObject):
|
||||||
self.updateInternals()
|
self.updateInternals()
|
||||||
self.updateStatusFromCache()
|
self.updateStatusFromCache()
|
||||||
|
|
||||||
|
def stopExecution(self):
|
||||||
|
""" Request graph execution to be stopped """
|
||||||
|
self.stopExecutionRequested.emit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def nodes(self):
|
def nodes(self):
|
||||||
return self._nodes
|
return self._nodes
|
||||||
|
@ -764,6 +799,7 @@ class Graph(BaseObject):
|
||||||
nodes = Property(BaseObject, nodes.fget, constant=True)
|
nodes = Property(BaseObject, nodes.fget, constant=True)
|
||||||
edges = Property(BaseObject, edges.fget, constant=True)
|
edges = Property(BaseObject, edges.fget, constant=True)
|
||||||
|
|
||||||
|
stopExecutionRequested = Signal()
|
||||||
|
|
||||||
def loadGraph(filepath):
|
def loadGraph(filepath):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -191,8 +191,8 @@ class StatisticsThread(threading.Thread):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.node = node
|
self.node = node
|
||||||
self.proc = None
|
self.proc = None
|
||||||
self.running = True
|
|
||||||
self.statistics = self.node.statistics
|
self.statistics = self.node.statistics
|
||||||
|
self._stopFlag = threading.Event()
|
||||||
|
|
||||||
def updateStats(self):
|
def updateStats(self):
|
||||||
self.lastTime = time.time()
|
self.lastTime = time.time()
|
||||||
|
@ -200,7 +200,14 @@ class StatisticsThread(threading.Thread):
|
||||||
self.node.saveStatistics()
|
self.node.saveStatistics()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while self.running:
|
while True:
|
||||||
self.updateStats()
|
self.updateStats()
|
||||||
time.sleep(60)
|
if self._stopFlag.wait(60):
|
||||||
|
# stopFlag has been set
|
||||||
|
# update stats one last time and exit main loop
|
||||||
|
self.updateStats()
|
||||||
|
return
|
||||||
|
|
||||||
|
def stopRequest(self):
|
||||||
|
""" Request the thread to exit as soon as possible. """
|
||||||
|
self._stopFlag.set()
|
||||||
|
|
|
@ -7,6 +7,7 @@ from PySide2.QtQml import QQmlApplicationEngine
|
||||||
from meshroom.ui.reconstruction import Reconstruction
|
from meshroom.ui.reconstruction import Reconstruction
|
||||||
from meshroom.ui.utils import QmlInstantEngine
|
from meshroom.ui.utils import QmlInstantEngine
|
||||||
|
|
||||||
|
from meshroom.ui import components
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -17,9 +18,14 @@ if __name__ == "__main__":
|
||||||
engine = QmlInstantEngine()
|
engine = QmlInstantEngine()
|
||||||
engine.addFilesFromDirectory(qmlDir)
|
engine.addFilesFromDirectory(qmlDir)
|
||||||
engine.setWatching(os.environ.get("MESHROOM_INSTANT_CODING", False))
|
engine.setWatching(os.environ.get("MESHROOM_INSTANT_CODING", False))
|
||||||
|
components.registerTypes()
|
||||||
|
|
||||||
r = Reconstruction()
|
r = Reconstruction()
|
||||||
engine.rootContext().setContextProperty("_reconstruction", r)
|
engine.rootContext().setContextProperty("_reconstruction", r)
|
||||||
|
|
||||||
|
# Request any potential computation to stop on exit
|
||||||
|
app.aboutToQuit.connect(r.stopExecution)
|
||||||
|
|
||||||
engine.load(os.path.normpath(url))
|
engine.load(os.path.normpath(url))
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ class AddNodeCommand(GraphCommand):
|
||||||
def redoImpl(self):
|
def redoImpl(self):
|
||||||
self.node = self.graph.addNewNode(self.nodeType)
|
self.node = self.graph.addNewNode(self.nodeType)
|
||||||
self.setText("Add Node {}".format(self.node.getName()))
|
self.setText("Add Node {}".format(self.node.getName()))
|
||||||
|
self.node._applyExpr()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def undoImpl(self):
|
def undoImpl(self):
|
||||||
|
@ -103,13 +104,14 @@ class RemoveNodeCommand(GraphCommand):
|
||||||
parent=self.graph, **self.nodeDesc["attributes"]
|
parent=self.graph, **self.nodeDesc["attributes"]
|
||||||
), self.nodeName)
|
), self.nodeName)
|
||||||
assert (node.getName() == self.nodeName)
|
assert (node.getName() == self.nodeName)
|
||||||
|
|
||||||
# recreate edges deleted on node removal
|
# recreate edges deleted on node removal
|
||||||
|
# edges having this node as destination could be retrieved from node description
|
||||||
|
# but we're missing edges starting from this node
|
||||||
for key, value in self.edges.items():
|
for key, value in self.edges.items():
|
||||||
iNode, iAttr = key.split(".")
|
dstNode, dstAttr = key.split(".")
|
||||||
oNode, oAttr = value.split(".")
|
srcNode, srcAttr = value.split(".")
|
||||||
self.graph.addEdge(self.graph.node(oNode).attribute(oAttr),
|
self.graph.addEdge(self.graph.node(srcNode).attribute(srcAttr),
|
||||||
self.graph.node(iNode).attribute(iAttr))
|
self.graph.node(dstNode).attribute(dstAttr))
|
||||||
|
|
||||||
node.updateInternals()
|
node.updateInternals()
|
||||||
|
|
||||||
|
@ -131,3 +133,38 @@ class SetAttributeCommand(GraphCommand):
|
||||||
|
|
||||||
def undoImpl(self):
|
def undoImpl(self):
|
||||||
self.graph.node(self.nodeName).attribute(self.attrName).value = self.oldValue
|
self.graph.node(self.nodeName).attribute(self.attrName).value = self.oldValue
|
||||||
|
|
||||||
|
|
||||||
|
class AddEdgeCommand(GraphCommand):
|
||||||
|
def __init__(self, graph, src, dst, parent=None):
|
||||||
|
super(AddEdgeCommand, self).__init__(graph, parent)
|
||||||
|
self.srcNode, self.srcAttr = src.fullName().split(".")
|
||||||
|
self.dstNode, self.dstAttr = dst.fullName().split(".")
|
||||||
|
self.setText("Connect '{}'->'{}'".format(src.fullName(), dst.fullName()))
|
||||||
|
|
||||||
|
def redoImpl(self):
|
||||||
|
try:
|
||||||
|
self.graph.addEdge(self.graph.node(self.srcNode).attribute(self.srcAttr),
|
||||||
|
self.graph.node(self.dstNode).attribute(self.dstAttr))
|
||||||
|
except RuntimeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def undoImpl(self):
|
||||||
|
self.graph.removeEdge(self.graph.node(self.dstNode).attribute(self.dstAttr))
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveEdgeCommand(GraphCommand):
|
||||||
|
def __init__(self, graph, edge, parent=None):
|
||||||
|
super(RemoveEdgeCommand, self).__init__(graph, parent)
|
||||||
|
self.srcNode, self.srcAttr = edge.src.fullName().split(".")
|
||||||
|
self.dstNode, self.dstAttr = edge.dst.fullName().split(".")
|
||||||
|
self.setText("Disconnect '{}'->'{}'".format(edge.src.fullName(), edge.dst.fullName()))
|
||||||
|
|
||||||
|
def redoImpl(self):
|
||||||
|
self.graph.removeEdge(self.graph.node(self.dstNode).attribute(self.dstAttr))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def undoImpl(self):
|
||||||
|
self.graph.addEdge(self.graph.node(self.srcNode).attribute(self.srcAttr),
|
||||||
|
self.graph.node(self.dstNode).attribute(self.dstAttr))
|
||||||
|
|
6
meshroom/ui/components/__init__.py
Executable file
6
meshroom/ui/components/__init__.py
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
def registerTypes():
|
||||||
|
from PySide2.QtQml import qmlRegisterType
|
||||||
|
from meshroom.ui.components.edge import EdgeMouseArea
|
||||||
|
|
||||||
|
qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
|
210
meshroom/ui/components/edge.py
Executable file
210
meshroom/ui/components/edge.py
Executable file
|
@ -0,0 +1,210 @@
|
||||||
|
from PySide2.QtCore import Signal, Property, QPointF, Qt, QObject
|
||||||
|
from PySide2.QtGui import QPainterPath, QVector2D
|
||||||
|
from PySide2.QtQuick import QQuickItem
|
||||||
|
|
||||||
|
|
||||||
|
class MouseEvent(QObject):
|
||||||
|
"""
|
||||||
|
Simple MouseEvent object, since QQuickMouseEvent is not accessible in the public API
|
||||||
|
"""
|
||||||
|
def __init__(self, evt):
|
||||||
|
super(MouseEvent, self).__init__()
|
||||||
|
self._x = evt.x()
|
||||||
|
self._y = evt.y()
|
||||||
|
self._button = evt.button()
|
||||||
|
|
||||||
|
x = Property(float, lambda self: self._x, constant=True)
|
||||||
|
y = Property(float, lambda self: self._y, constant=True)
|
||||||
|
button = Property(Qt.MouseButton, lambda self: self._button, constant=True)
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeMouseArea(QQuickItem):
|
||||||
|
"""
|
||||||
|
Provides a MouseArea shaped as a cubic spline for mouse interaction with edges.
|
||||||
|
|
||||||
|
Note: for performance reason, shape is updated only when geometry changes since this is the main use-case with edges.
|
||||||
|
TODOs:
|
||||||
|
- update when start/end points change too
|
||||||
|
- review this when using new QML Shape module
|
||||||
|
"""
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(EdgeMouseArea, self).__init__(parent)
|
||||||
|
|
||||||
|
self._viewScale = 1.0
|
||||||
|
self._startX = 0.0
|
||||||
|
self._startY = 0.0
|
||||||
|
self._endX = 0.0
|
||||||
|
self._endY = 0.0
|
||||||
|
self._curveScale = 0.7
|
||||||
|
self._edgeThickness = 1.0
|
||||||
|
self._hullThickness = 2.0
|
||||||
|
self._containsMouse = False
|
||||||
|
self._path = None # type: QPainterPath
|
||||||
|
|
||||||
|
self.setAcceptHoverEvents(True)
|
||||||
|
self.setAcceptedMouseButtons(Qt.AllButtons)
|
||||||
|
|
||||||
|
def contains(self, point):
|
||||||
|
return self._path.contains(point)
|
||||||
|
|
||||||
|
def hoverEnterEvent(self, evt):
|
||||||
|
self.setContainsMouse(True)
|
||||||
|
super(EdgeMouseArea, self).hoverEnterEvent(evt)
|
||||||
|
|
||||||
|
def hoverLeaveEvent(self, evt):
|
||||||
|
self.setContainsMouse(False)
|
||||||
|
super(EdgeMouseArea, self).hoverLeaveEvent(evt)
|
||||||
|
|
||||||
|
def geometryChanged(self, newGeometry, oldGeometry):
|
||||||
|
super(EdgeMouseArea, self).geometryChanged(newGeometry, oldGeometry)
|
||||||
|
self.updateShape()
|
||||||
|
|
||||||
|
def mousePressEvent(self, evt):
|
||||||
|
if not self.acceptedMouseButtons() & evt.button():
|
||||||
|
evt.setAccepted(False)
|
||||||
|
return
|
||||||
|
e = MouseEvent(evt)
|
||||||
|
self.pressed.emit(e)
|
||||||
|
e.deleteLater()
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, evt):
|
||||||
|
e = MouseEvent(evt)
|
||||||
|
self.released.emit(e)
|
||||||
|
e.deleteLater()
|
||||||
|
|
||||||
|
def updateShape(self):
|
||||||
|
p1 = QPointF(self._startX, self._startY)
|
||||||
|
p2 = QPointF(self._endX, self._endY)
|
||||||
|
ctrlPt = QPointF(self.ctrlPtDist, 0)
|
||||||
|
path = QPainterPath(p1)
|
||||||
|
path.cubicTo(p1 + ctrlPt, p2 - ctrlPt, p2)
|
||||||
|
|
||||||
|
# Compute offset on x and y axis
|
||||||
|
hullOffset = self._edgeThickness * self._viewScale + self._hullThickness
|
||||||
|
v = QVector2D(p2 - p1).normalized()
|
||||||
|
offset = QPointF(hullOffset * -v.y(), hullOffset * v.x())
|
||||||
|
|
||||||
|
self._path = QPainterPath(path.toReversed())
|
||||||
|
self._path.translate(-offset)
|
||||||
|
path.translate(offset)
|
||||||
|
self._path.connectPath(path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def thickness(self):
|
||||||
|
return self._hullThickness
|
||||||
|
|
||||||
|
@thickness.setter
|
||||||
|
def thickness(self, value):
|
||||||
|
if self._hullThickness == value:
|
||||||
|
return
|
||||||
|
self._hullThickness = value
|
||||||
|
self.thicknessChanged.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edgeThickness(self):
|
||||||
|
return self._edgeThickness
|
||||||
|
|
||||||
|
@edgeThickness.setter
|
||||||
|
def edgeThickness(self, value):
|
||||||
|
if self._edgeThickness == value:
|
||||||
|
return
|
||||||
|
self._edgeThickness = value
|
||||||
|
self.thicknessChanged.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def viewScale(self):
|
||||||
|
return self._viewScale
|
||||||
|
|
||||||
|
@viewScale.setter
|
||||||
|
def viewScale(self, value):
|
||||||
|
if self.viewScale == value:
|
||||||
|
return
|
||||||
|
self._viewScale = value
|
||||||
|
self.viewScaleChanged.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def startX(self):
|
||||||
|
return self._startX
|
||||||
|
|
||||||
|
@startX.setter
|
||||||
|
def startX(self, value):
|
||||||
|
self._startX = value
|
||||||
|
self.startXChanged.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def startY(self):
|
||||||
|
return self._startY
|
||||||
|
|
||||||
|
@startY.setter
|
||||||
|
def startY(self, value):
|
||||||
|
self._startY = value
|
||||||
|
self.startYChanged.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endX(self):
|
||||||
|
return self._endX
|
||||||
|
|
||||||
|
@endX.setter
|
||||||
|
def endX(self, value):
|
||||||
|
self._endX = value
|
||||||
|
self.endXChanged.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endY(self):
|
||||||
|
return self._endY
|
||||||
|
|
||||||
|
@endY.setter
|
||||||
|
def endY(self, value):
|
||||||
|
self._endY = value
|
||||||
|
self.endYChanged.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def curveScale(self):
|
||||||
|
return self._curveScale
|
||||||
|
|
||||||
|
@curveScale.setter
|
||||||
|
def curveScale(self, value):
|
||||||
|
self._curveScale = value
|
||||||
|
self.curveScaleChanged.emit()
|
||||||
|
self.updateShape()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ctrlPtDist(self):
|
||||||
|
return self.width() * self.curveScale * (-1 if self._startX > self._endX else 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def containsMouse(self):
|
||||||
|
return self._containsMouse
|
||||||
|
|
||||||
|
def setContainsMouse(self, value):
|
||||||
|
if self._containsMouse == value:
|
||||||
|
return
|
||||||
|
self._containsMouse = value
|
||||||
|
self.containsMouseChanged.emit()
|
||||||
|
|
||||||
|
thicknessChanged = Signal()
|
||||||
|
thickness = Property(float, thickness.fget, thickness.fset, notify=thicknessChanged)
|
||||||
|
edgeThicknessChanged = Signal()
|
||||||
|
edgeThickness = Property(float, edgeThickness.fget, edgeThickness.fset, notify=edgeThicknessChanged)
|
||||||
|
viewScaleChanged = Signal()
|
||||||
|
viewScale = Property(float, viewScale.fget, viewScale.fset, notify=viewScaleChanged)
|
||||||
|
startXChanged = Signal()
|
||||||
|
startX = Property(float, startX.fget, startX.fset, notify=startXChanged)
|
||||||
|
startYChanged = Signal()
|
||||||
|
startY = Property(float, startY.fget, startY.fset, notify=startYChanged)
|
||||||
|
endXChanged = Signal()
|
||||||
|
endX = Property(float, endX.fget, endX.fset, notify=endXChanged)
|
||||||
|
endYChanged = Signal()
|
||||||
|
endY = Property(float, endY.fget, endY.fset, notify=endYChanged)
|
||||||
|
curveScaleChanged = Signal()
|
||||||
|
curveScale = Property(float, curveScale.fget, curveScale.fset, notify=curveScaleChanged)
|
||||||
|
ctrlPtDistChanged = Signal()
|
||||||
|
ctrlPtDist = Property(float, ctrlPtDist.fget, notify=ctrlPtDist)
|
||||||
|
containsMouseChanged = Signal()
|
||||||
|
containsMouse = Property(float, containsMouse.fget, notify=containsMouseChanged)
|
||||||
|
acceptedButtons = Property(int,
|
||||||
|
lambda self: super(EdgeMouseArea, self).acceptedMouseButtons,
|
||||||
|
lambda self, value: super(EdgeMouseArea, self).setAcceptedMouseButtons(value))
|
||||||
|
|
||||||
|
pressed = Signal(MouseEvent)
|
||||||
|
released = Signal(MouseEvent)
|
|
@ -1,32 +1,68 @@
|
||||||
from PySide2 import QtCore
|
from threading import Thread
|
||||||
|
|
||||||
|
from PySide2.QtCore import QObject, Slot, Property, Signal
|
||||||
|
|
||||||
from meshroom.core import graph
|
from meshroom.core import graph
|
||||||
from meshroom.ui import commands
|
from meshroom.ui import commands
|
||||||
|
|
||||||
|
|
||||||
class Reconstruction(QtCore.QObject):
|
class Reconstruction(QObject):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(Reconstruction, self).__init__(parent)
|
super(Reconstruction, self).__init__(parent)
|
||||||
self._graph = graph.Graph("")
|
self._graph = graph.Graph("")
|
||||||
self._undoStack = commands.UndoStack(self)
|
self._undoStack = commands.UndoStack(self)
|
||||||
|
self._computeThread = Thread()
|
||||||
|
|
||||||
@QtCore.Slot(str)
|
@Slot(str)
|
||||||
def addNode(self, nodeType):
|
def addNode(self, nodeType):
|
||||||
self._undoStack.tryAndPush(commands.AddNodeCommand(self._graph, nodeType))
|
self._undoStack.tryAndPush(commands.AddNodeCommand(self._graph, nodeType))
|
||||||
|
|
||||||
@QtCore.Slot(graph.Node)
|
@Slot(graph.Node)
|
||||||
def removeNode(self, node):
|
def removeNode(self, node):
|
||||||
self._undoStack.tryAndPush(commands.RemoveNodeCommand(self._graph, node))
|
self._undoStack.tryAndPush(commands.RemoveNodeCommand(self._graph, node))
|
||||||
|
|
||||||
@QtCore.Slot(graph.Attribute, "QVariant")
|
@Slot(graph.Attribute, graph.Attribute)
|
||||||
|
def addEdge(self, src, dst):
|
||||||
|
self._undoStack.tryAndPush(commands.AddEdgeCommand(self._graph, src, dst))
|
||||||
|
|
||||||
|
@Slot(graph.Edge)
|
||||||
|
def removeEdge(self, edge):
|
||||||
|
self._undoStack.tryAndPush(commands.RemoveEdgeCommand(self._graph, edge))
|
||||||
|
|
||||||
|
@Slot(graph.Attribute, "QVariant")
|
||||||
def setAttribute(self, attribute, value):
|
def setAttribute(self, attribute, value):
|
||||||
self._undoStack.tryAndPush(commands.SetAttributeCommand(self._graph, attribute, value))
|
self._undoStack.tryAndPush(commands.SetAttributeCommand(self._graph, attribute, value))
|
||||||
|
|
||||||
@QtCore.Slot(str)
|
@Slot(str)
|
||||||
def load(self, filepath):
|
def load(self, filepath):
|
||||||
self._graph.load(filepath)
|
self._graph.load(filepath)
|
||||||
|
self._graph.update()
|
||||||
|
self._undoStack.clear()
|
||||||
|
|
||||||
undoStack = QtCore.Property(QtCore.QObject, lambda self: self._undoStack, constant=True)
|
@Slot(graph.Node)
|
||||||
graph = QtCore.Property(QtCore.QObject, lambda self: self._graph, constant=True)
|
def execute(self, node=None):
|
||||||
nodes = QtCore.Property(QtCore.QObject, lambda self: self._graph.nodes, constant=True)
|
if self.computing:
|
||||||
|
return
|
||||||
|
nodes = [node] if node else self._graph.getLeaves()
|
||||||
|
self._computeThread = Thread(target=self._execute, args=(nodes,))
|
||||||
|
self._computeThread.start()
|
||||||
|
|
||||||
|
def _execute(self, nodes):
|
||||||
|
self.computingChanged.emit()
|
||||||
|
graph.execute(self._graph, nodes)
|
||||||
|
self.computingChanged.emit()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def stopExecution(self):
|
||||||
|
if not self.computing:
|
||||||
|
return
|
||||||
|
self._graph.stopExecution()
|
||||||
|
self._computeThread.join()
|
||||||
|
self.computingChanged.emit()
|
||||||
|
|
||||||
|
undoStack = Property(QObject, lambda self: self._undoStack, constant=True)
|
||||||
|
graph = Property(graph.Graph, lambda self: self._graph, constant=True)
|
||||||
|
nodes = Property(QObject, lambda self: self._graph.nodes, constant=True)
|
||||||
|
computingChanged = Signal()
|
||||||
|
computing = Property(bool, lambda self: self._computeThread.is_alive(), notify=computingChanged)
|
|
@ -13,9 +13,9 @@ def test_depth():
|
||||||
(tB.output, tC.input)
|
(tB.output, tC.input)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert tA.getDepth() == 1
|
assert tA.depth == 1
|
||||||
assert tB.getDepth() == 2
|
assert tB.depth == 2
|
||||||
assert tC.getDepth() == 3
|
assert tC.depth == 3
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue