mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-07-15 15:55:18 +02:00
[core] Node: Clean-up code
This commit is contained in:
parent
081d38f78d
commit
8300626ef5
1 changed files with 142 additions and 93 deletions
|
@ -56,6 +56,8 @@ class Status(Enum):
|
||||||
|
|
||||||
|
|
||||||
class ExecMode(Enum):
|
class ExecMode(Enum):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
NONE = auto()
|
NONE = auto()
|
||||||
LOCAL = auto()
|
LOCAL = auto()
|
||||||
EXTERN = auto()
|
EXTERN = auto()
|
||||||
|
@ -66,7 +68,8 @@ class StatusData(BaseObject):
|
||||||
"""
|
"""
|
||||||
dateTimeFormatting = '%Y-%m-%d %H:%M:%S.%f'
|
dateTimeFormatting = '%Y-%m-%d %H:%M:%S.%f'
|
||||||
|
|
||||||
def __init__(self, nodeName='', nodeType='', packageName='', packageVersion='', mrNodeType: MrNodeType = MrNodeType.NONE, parent: BaseObject = None):
|
def __init__(self, nodeName='', nodeType='', packageName='', packageVersion='',
|
||||||
|
mrNodeType: MrNodeType = MrNodeType.NONE, parent: BaseObject = None):
|
||||||
super(StatusData, self).__init__(parent)
|
super(StatusData, self).__init__(parent)
|
||||||
|
|
||||||
self.nodeName: str = nodeName
|
self.nodeName: str = nodeName
|
||||||
|
@ -81,12 +84,13 @@ class StatusData(BaseObject):
|
||||||
self.resetDynamicValues()
|
self.resetDynamicValues()
|
||||||
|
|
||||||
def setNode(self, node):
|
def setNode(self, node):
|
||||||
""" Set the node information from one node instance."""
|
""" Set the node information from one node instance. """
|
||||||
self.nodeName = node.name
|
self.nodeName = node.name
|
||||||
self.setNodeType(node)
|
self.setNodeType(node)
|
||||||
|
|
||||||
def setNodeType(self, node):
|
def setNodeType(self, node):
|
||||||
""" Set the node type and package information from the given node.
|
"""
|
||||||
|
Set the node type and package information from the given node.
|
||||||
We do not set the name in this method as it may vary if there are duplicates.
|
We do not set the name in this method as it may vary if there are duplicates.
|
||||||
"""
|
"""
|
||||||
self.nodeType = node.nodeType
|
self.nodeType = node.nodeType
|
||||||
|
@ -132,17 +136,21 @@ class StatusData(BaseObject):
|
||||||
# we don't want to modify the execMode set from the submit.
|
# we don't want to modify the execMode set from the submit.
|
||||||
|
|
||||||
def initIsolatedCompute(self):
|
def initIsolatedCompute(self):
|
||||||
''' When submitting a node, we reset the status information to ensure that we do not keep outdated information.
|
"""
|
||||||
'''
|
When submitting a node, we reset the status information to ensure that we do not keep
|
||||||
|
outdated information.
|
||||||
|
"""
|
||||||
self.resetDynamicValues()
|
self.resetDynamicValues()
|
||||||
self.initStartCompute()
|
self.initStartCompute()
|
||||||
assert(self.mrNodeType == MrNodeType.NODE)
|
assert self.mrNodeType == MrNodeType.NODE
|
||||||
self.sessionUid = None
|
self.sessionUid = None
|
||||||
self.submitterSessionUid = meshroom.core.sessionUid
|
self.submitterSessionUid = meshroom.core.sessionUid
|
||||||
|
|
||||||
def initExternSubmit(self):
|
def initExternSubmit(self):
|
||||||
''' When submitting a node, we reset the status information to ensure that we do not keep outdated information.
|
"""
|
||||||
'''
|
When submitting a node, we reset the status information to ensure that we do not keep
|
||||||
|
outdated information.
|
||||||
|
"""
|
||||||
self.resetDynamicValues()
|
self.resetDynamicValues()
|
||||||
self.sessionUid = None
|
self.sessionUid = None
|
||||||
self.submitterSessionUid = meshroom.core.sessionUid
|
self.submitterSessionUid = meshroom.core.sessionUid
|
||||||
|
@ -150,8 +158,10 @@ class StatusData(BaseObject):
|
||||||
self.execMode = ExecMode.EXTERN
|
self.execMode = ExecMode.EXTERN
|
||||||
|
|
||||||
def initLocalSubmit(self):
|
def initLocalSubmit(self):
|
||||||
''' When submitting a node, we reset the status information to ensure that we do not keep outdated information.
|
"""
|
||||||
'''
|
When submitting a node, we reset the status information to ensure that we do not keep
|
||||||
|
outdated information.
|
||||||
|
"""
|
||||||
self.resetDynamicValues()
|
self.resetDynamicValues()
|
||||||
self.sessionUid = None
|
self.sessionUid = None
|
||||||
self.submitterSessionUid = meshroom.core.sessionUid
|
self.submitterSessionUid = meshroom.core.sessionUid
|
||||||
|
@ -181,29 +191,29 @@ class StatusData(BaseObject):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def fromDict(self, d):
|
def fromDict(self, d):
|
||||||
self.status = d.get('status', Status.NONE)
|
self.status = d.get("status", Status.NONE)
|
||||||
if not isinstance(self.status, Status):
|
if not isinstance(self.status, Status):
|
||||||
self.status = Status[self.status]
|
self.status = Status[self.status]
|
||||||
self.execMode = d.get('execMode', ExecMode.NONE)
|
self.execMode = d.get("execMode", ExecMode.NONE)
|
||||||
if not isinstance(self.execMode, ExecMode):
|
if not isinstance(self.execMode, ExecMode):
|
||||||
self.execMode = ExecMode[self.execMode]
|
self.execMode = ExecMode[self.execMode]
|
||||||
self.mrNodeType = d.get('mrNodeType', MrNodeType.NONE)
|
self.mrNodeType = d.get("mrNodeType", MrNodeType.NONE)
|
||||||
if not isinstance(self.mrNodeType, MrNodeType):
|
if not isinstance(self.mrNodeType, MrNodeType):
|
||||||
self.mrNodeType = MrNodeType[self.mrNodeType]
|
self.mrNodeType = MrNodeType[self.mrNodeType]
|
||||||
|
|
||||||
self.nodeName = d.get('nodeName', '')
|
self.nodeName = d.get("nodeName", "")
|
||||||
self.nodeType = d.get('nodeType', '')
|
self.nodeType = d.get("nodeType", "")
|
||||||
self.packageName = d.get('packageName', '')
|
self.packageName = d.get("packageName", "")
|
||||||
self.packageVersion = d.get('packageVersion', '')
|
self.packageVersion = d.get("packageVersion", "")
|
||||||
self.graph = d.get('graph', '')
|
self.graph = d.get("graph", "")
|
||||||
self.commandLine = d.get('commandLine', '')
|
self.commandLine = d.get("commandLine", "")
|
||||||
self.env = d.get('env', '')
|
self.env = d.get("env", "")
|
||||||
self.startDateTime = d.get('startDateTime', '')
|
self.startDateTime = d.get("startDateTime", "")
|
||||||
self.endDateTime = d.get('endDateTime', '')
|
self.endDateTime = d.get("endDateTime", "")
|
||||||
self.elapsedTime = d.get('elapsedTime', 0)
|
self.elapsedTime = d.get("elapsedTime", 0)
|
||||||
self.hostname = d.get('hostname', '')
|
self.hostname = d.get("hostname", "")
|
||||||
self.sessionUid = d.get('sessionUid', '')
|
self.sessionUid = d.get("sessionUid", "")
|
||||||
self.submitterSessionUid = d.get('submitterSessionUid', '')
|
self.submitterSessionUid = d.get("submitterSessionUid", "")
|
||||||
|
|
||||||
|
|
||||||
class LogManager:
|
class LogManager:
|
||||||
|
@ -223,7 +233,8 @@ class LogManager:
|
||||||
for handler in self.logger.handlers[:]:
|
for handler in self.logger.handlers[:]:
|
||||||
self.logger.removeHandler(handler)
|
self.logger.removeHandler(handler)
|
||||||
handler = logging.FileHandler(self.chunk.logFile)
|
handler = logging.FileHandler(self.chunk.logFile)
|
||||||
formatter = self.Formatter('[%(asctime)s.%(msecs)03d][%(levelname)s] %(message)s', self.dateTimeFormatting)
|
formatter = self.Formatter('[%(asctime)s.%(msecs)03d][%(levelname)s] %(message)s',
|
||||||
|
self.dateTimeFormatting)
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
self.logger.addHandler(handler)
|
self.logger.addHandler(handler)
|
||||||
|
|
||||||
|
@ -284,15 +295,15 @@ class LogManager:
|
||||||
self.progressBar = False
|
self.progressBar = False
|
||||||
|
|
||||||
def textToLevel(self, text):
|
def textToLevel(self, text):
|
||||||
if text == 'critical':
|
if text == "critical":
|
||||||
return logging.CRITICAL
|
return logging.CRITICAL
|
||||||
elif text == 'error':
|
elif text == "error":
|
||||||
return logging.ERROR
|
return logging.ERROR
|
||||||
elif text == 'warning':
|
elif text == "warning":
|
||||||
return logging.WARNING
|
return logging.WARNING
|
||||||
elif text == 'info':
|
elif text == "info":
|
||||||
return logging.INFO
|
return logging.INFO
|
||||||
elif text == 'debug':
|
elif text == "debug":
|
||||||
return logging.DEBUG
|
return logging.DEBUG
|
||||||
else:
|
else:
|
||||||
return logging.NOTSET
|
return logging.NOTSET
|
||||||
|
@ -313,7 +324,8 @@ class NodeChunk(BaseObject):
|
||||||
self.node = node
|
self.node = node
|
||||||
self.range = range
|
self.range = range
|
||||||
self.logManager: LogManager = LogManager(self)
|
self.logManager: LogManager = LogManager(self)
|
||||||
self._status: StatusData = StatusData(node.name, node.nodeType, node.packageName, node.packageVersion, node.getMrNodeType())
|
self._status: StatusData = StatusData(node.name, node.nodeType, node.packageName,
|
||||||
|
node.packageVersion, node.getMrNodeType())
|
||||||
self.statistics: stats.Statistics = stats.Statistics()
|
self.statistics: stats.Statistics = stats.Statistics()
|
||||||
self.statusFileLastModTime = -1
|
self.statusFileLastModTime = -1
|
||||||
self.subprocess = None
|
self.subprocess = None
|
||||||
|
@ -375,21 +387,24 @@ class NodeChunk(BaseObject):
|
||||||
if self.range.blockSize == 0:
|
if self.range.blockSize == 0:
|
||||||
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, "status")
|
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, "status")
|
||||||
else:
|
else:
|
||||||
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, str(self.index) + ".status")
|
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder,
|
||||||
|
str(self.index) + ".status")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def statisticsFile(self):
|
def statisticsFile(self):
|
||||||
if self.range.blockSize == 0:
|
if self.range.blockSize == 0:
|
||||||
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, "statistics")
|
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, "statistics")
|
||||||
else:
|
else:
|
||||||
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, str(self.index) + ".statistics")
|
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder,
|
||||||
|
str(self.index) + ".statistics")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def logFile(self):
|
def logFile(self):
|
||||||
if self.range.blockSize == 0:
|
if self.range.blockSize == 0:
|
||||||
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, "log")
|
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, "log")
|
||||||
else:
|
else:
|
||||||
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder, str(self.index) + ".log")
|
return os.path.join(self.node.graph.cacheDir, self.node.internalFolder,
|
||||||
|
str(self.index) + ".log")
|
||||||
|
|
||||||
def saveStatusFile(self):
|
def saveStatusFile(self):
|
||||||
"""
|
"""
|
||||||
|
@ -406,7 +421,8 @@ class NodeChunk(BaseObject):
|
||||||
renameWritingToFinalPath(statusFilepathWriting, statusFilepath)
|
renameWritingToFinalPath(statusFilepathWriting, statusFilepath)
|
||||||
|
|
||||||
def upgradeStatusFile(self):
|
def upgradeStatusFile(self):
|
||||||
""" Upgrade node status file based on the current status.
|
"""
|
||||||
|
Upgrade node status file based on the current status.
|
||||||
"""
|
"""
|
||||||
self.saveStatusFile()
|
self.saveStatusFile()
|
||||||
self.statusChanged.emit()
|
self.statusChanged.emit()
|
||||||
|
@ -468,7 +484,8 @@ class NodeChunk(BaseObject):
|
||||||
|
|
||||||
# Start the process environment for nodes running in isolation.
|
# Start the process environment for nodes running in isolation.
|
||||||
# This only happens once, when the node has the SUBMITTED status.
|
# This only happens once, when the node has the SUBMITTED status.
|
||||||
# The sub-process will go through this method again, but the node status will have been set to RUNNING.
|
# The sub-process will go through this method again, but the node status will
|
||||||
|
# have been set to RUNNING.
|
||||||
if not inCurrentEnv and self.node.getMrNodeType() == MrNodeType.NODE:
|
if not inCurrentEnv and self.node.getMrNodeType() == MrNodeType.NODE:
|
||||||
self._processInIsolatedEnvironment()
|
self._processInIsolatedEnvironment()
|
||||||
return
|
return
|
||||||
|
@ -541,12 +558,14 @@ class NodeChunk(BaseObject):
|
||||||
self.updateStatusFromCache()
|
self.updateStatusFromCache()
|
||||||
|
|
||||||
if self._status.status != Status.RUNNING:
|
if self._status.status != Status.RUNNING:
|
||||||
# When we stop the process of a node with multiple chunks, the Node function will call the stop function of each chunk.
|
# When we stop the process of a node with multiple chunks, the Node function will call
|
||||||
# So, the chunck status could be SUBMITTED, RUNNING or ERROR.
|
# the stop function of each chunk.
|
||||||
|
# So, the chunk status could be SUBMITTED, RUNNING or ERROR.
|
||||||
|
|
||||||
if self._status.status is Status.SUBMITTED:
|
if self._status.status is Status.SUBMITTED:
|
||||||
self.upgradeStatusTo(Status.NONE)
|
self.upgradeStatusTo(Status.NONE)
|
||||||
elif self._status.status in (Status.ERROR, Status.STOPPED, Status.KILLED, Status.SUCCESS, Status.NONE):
|
elif self._status.status in (Status.ERROR, Status.STOPPED, Status.KILLED,
|
||||||
|
Status.SUCCESS, Status.NONE):
|
||||||
# Nothing to do, the computation is already stopped.
|
# Nothing to do, the computation is already stopped.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -560,8 +579,10 @@ class NodeChunk(BaseObject):
|
||||||
self.upgradeStatusTo(Status.STOPPED)
|
self.upgradeStatusTo(Status.STOPPED)
|
||||||
|
|
||||||
def isExtern(self):
|
def isExtern(self):
|
||||||
""" The computation is managed externally by another instance of Meshroom.
|
"""
|
||||||
In the ambiguous case of an isolated environment, it is considered as local as we can stop it (if it is run from the current Meshroom instance).
|
The computation is managed externally by another instance of Meshroom.
|
||||||
|
In the ambiguous case of an isolated environment, it is considered as local as we can stop
|
||||||
|
it (if it is run from the current Meshroom instance).
|
||||||
"""
|
"""
|
||||||
if self._status.execMode == ExecMode.EXTERN:
|
if self._status.execMode == ExecMode.EXTERN:
|
||||||
return True
|
return True
|
||||||
|
@ -603,7 +624,8 @@ class BaseNode(BaseObject):
|
||||||
# i.e: a.b, a[0], a[0].b.c[1]
|
# i.e: a.b, a[0], a[0].b.c[1]
|
||||||
attributeRE = re.compile(r'\.?(?P<name>\w+)(?:\[(?P<index>\d+)\])?')
|
attributeRE = re.compile(r'\.?(?P<name>\w+)(?:\[(?P<index>\d+)\])?')
|
||||||
|
|
||||||
def __init__(self, nodeType: str, position: Position = None, parent: BaseObject = None, uid: str = None, **kwargs):
|
def __init__(self, nodeType: str, position: Position = None, parent: BaseObject = None,
|
||||||
|
uid: str = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a new Node instance based on the given node description.
|
Create a new Node instance based on the given node description.
|
||||||
Any other keyword argument will be used to initialize this node's attributes.
|
Any other keyword argument will be used to initialize this node's attributes.
|
||||||
|
@ -656,7 +678,8 @@ class BaseNode(BaseObject):
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def getMrNodeType(self):
|
def getMrNodeType(self):
|
||||||
# In compatibility mode, we may or may not have access to the nodeDesc and its information about the node type.
|
# In compatibility mode, we may or may not have access to the nodeDesc and its information
|
||||||
|
# about the node type.
|
||||||
if self.nodeDesc is None:
|
if self.nodeDesc is None:
|
||||||
return MrNodeType.NONE
|
return MrNodeType.NONE
|
||||||
return self.nodeDesc.getMrNodeType()
|
return self.nodeDesc.getMrNodeType()
|
||||||
|
@ -826,22 +849,25 @@ class BaseNode(BaseObject):
|
||||||
return os.path.join(self.graph.cacheDir, self.internalFolder, 'values')
|
return os.path.join(self.graph.cacheDir, self.internalFolder, 'values')
|
||||||
|
|
||||||
def getInputNodes(self, recursive, dependenciesOnly):
|
def getInputNodes(self, recursive, dependenciesOnly):
|
||||||
return self.graph.getInputNodes(self, recursive=recursive, dependenciesOnly=dependenciesOnly)
|
return self.graph.getInputNodes(self, recursive=recursive,
|
||||||
|
dependenciesOnly=dependenciesOnly)
|
||||||
|
|
||||||
def getOutputNodes(self, recursive, dependenciesOnly):
|
def getOutputNodes(self, recursive, dependenciesOnly):
|
||||||
return self.graph.getOutputNodes(self, recursive=recursive, dependenciesOnly=dependenciesOnly)
|
return self.graph.getOutputNodes(self, recursive=recursive,
|
||||||
|
dependenciesOnly=dependenciesOnly)
|
||||||
|
|
||||||
def toDict(self):
|
def toDict(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _computeUid(self):
|
def _computeUid(self):
|
||||||
""" Compute node UID by combining associated attributes' UIDs. """
|
""" Compute node UID by combining associated attributes' UIDs. """
|
||||||
# If there is no invalidating attribute, then the computation of the UID should not go through as
|
# If there is no invalidating attribute, then the computation of the UID should not
|
||||||
# it will only include the node type
|
# go through as it will only include the node type
|
||||||
if not self.invalidatingAttributes:
|
if not self.invalidatingAttributes:
|
||||||
return
|
return
|
||||||
|
|
||||||
# UID is computed by hashing the sorted list of tuple (name, value) of all attributes impacting this UID
|
# UID is computed by hashing the sorted list of tuple (name, value) of all attributes
|
||||||
|
# impacting this UID
|
||||||
uidAttributes = []
|
uidAttributes = []
|
||||||
for attr in self.invalidatingAttributes:
|
for attr in self.invalidatingAttributes:
|
||||||
if not attr.enabled:
|
if not attr.enabled:
|
||||||
|
@ -862,6 +888,10 @@ class BaseNode(BaseObject):
|
||||||
self._uid = hashValue(uidAttributes)
|
self._uid = hashValue(uidAttributes)
|
||||||
|
|
||||||
def _buildCmdVars(self):
|
def _buildCmdVars(self):
|
||||||
|
"""
|
||||||
|
Generate command variables using input attributes and resolved output attributes
|
||||||
|
names and values.
|
||||||
|
"""
|
||||||
def _buildAttributeCmdVars(cmdVars, name, attr):
|
def _buildAttributeCmdVars(cmdVars, name, attr):
|
||||||
if attr.enabled:
|
if attr.enabled:
|
||||||
group = attr.attributeDesc.group(attr.node) \
|
group = attr.attributeDesc.group(attr.node) \
|
||||||
|
@ -886,7 +916,6 @@ class BaseNode(BaseObject):
|
||||||
for v in attr._value:
|
for v in attr._value:
|
||||||
_buildAttributeCmdVars(cmdVars, v.name, v)
|
_buildAttributeCmdVars(cmdVars, v.name, v)
|
||||||
|
|
||||||
""" Generate command variables using input attributes and resolved output attributes names and values. """
|
|
||||||
self._cmdVars["uid"] = self._uid
|
self._cmdVars["uid"] = self._uid
|
||||||
self._cmdVars["nodeCacheFolder"] = self.internalFolder
|
self._cmdVars["nodeCacheFolder"] = self.internalFolder
|
||||||
self._cmdVars["nodeSourceCodeFolder"] = self.sourceCodeFolder
|
self._cmdVars["nodeSourceCodeFolder"] = self.sourceCodeFolder
|
||||||
|
@ -901,8 +930,9 @@ class BaseNode(BaseObject):
|
||||||
cmdVarsNoCache = self._cmdVars.copy()
|
cmdVarsNoCache = self._cmdVars.copy()
|
||||||
cmdVarsNoCache["cache"] = ""
|
cmdVarsNoCache["cache"] = ""
|
||||||
|
|
||||||
# Use "self._internalFolder" instead of "self.internalFolder" because we do not want it to be
|
# Use "self._internalFolder" instead of "self.internalFolder" because we do not want it to
|
||||||
# resolved with the {cache} information ("self.internalFolder" resolves "self._internalFolder")
|
# be resolved with the {cache} information ("self.internalFolder" resolves
|
||||||
|
# "self._internalFolder")
|
||||||
cmdVarsNoCache["nodeCacheFolder"] = self._internalFolder.format(**cmdVarsNoCache)
|
cmdVarsNoCache["nodeCacheFolder"] = self._internalFolder.format(**cmdVarsNoCache)
|
||||||
|
|
||||||
# Evaluate output params
|
# Evaluate output params
|
||||||
|
@ -919,8 +949,8 @@ class BaseNode(BaseObject):
|
||||||
try:
|
try:
|
||||||
defaultValue = attr.defaultValue()
|
defaultValue = attr.defaultValue()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# If we load an old scene, the lambda associated to the 'value' could try to access other
|
# If we load an old scene, the lambda associated to the 'value' could try to
|
||||||
# params that could not exist yet
|
# access other params that could not exist yet
|
||||||
logging.warning('Invalid lambda evaluation for "{nodeName}.{attrName}"'.
|
logging.warning('Invalid lambda evaluation for "{nodeName}.{attrName}"'.
|
||||||
format(nodeName=self.name, attrName=attr.name))
|
format(nodeName=self.name, attrName=attr.name))
|
||||||
if defaultValue is not None:
|
if defaultValue is not None:
|
||||||
|
@ -945,8 +975,8 @@ class BaseNode(BaseObject):
|
||||||
self._cmdVars[name + 'Value'] = attr.getValueStr(withQuotes=False)
|
self._cmdVars[name + 'Value'] = attr.getValueStr(withQuotes=False)
|
||||||
|
|
||||||
if v:
|
if v:
|
||||||
self._cmdVars[attr.attributeDesc.group] = self._cmdVars.get(attr.attributeDesc.group, '') + \
|
self._cmdVars[attr.attributeDesc.group] = \
|
||||||
' ' + self._cmdVars[name]
|
self._cmdVars.get(attr.attributeDesc.group, '') + ' ' + self._cmdVars[name]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isParallelized(self):
|
def isParallelized(self):
|
||||||
|
@ -958,7 +988,7 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
def hasStatus(self, status: Status):
|
def hasStatus(self, status: Status):
|
||||||
if not self._chunks:
|
if not self._chunks:
|
||||||
return (status == Status.INPUT)
|
return status == Status.INPUT
|
||||||
for chunk in self._chunks:
|
for chunk in self._chunks:
|
||||||
if chunk.status.status != status:
|
if chunk.status.status != status:
|
||||||
return False
|
return False
|
||||||
|
@ -973,7 +1003,8 @@ class BaseNode(BaseObject):
|
||||||
""" Return True if this node type is computable, False otherwise.
|
""" Return True if this node type is computable, False otherwise.
|
||||||
A computable node type can be in a context that does not allow computation.
|
A computable node type can be in a context that does not allow computation.
|
||||||
"""
|
"""
|
||||||
# Ambiguous case for NONE, which could be used for compatibility nodes if we don't have any information about the node descriptor.
|
# Ambiguous case for NONE, which could be used for compatibility nodes if we don't have
|
||||||
|
# any information about the node descriptor.
|
||||||
return self.getMrNodeType() != MrNodeType.INPUT
|
return self.getMrNodeType() != MrNodeType.INPUT
|
||||||
|
|
||||||
def clearData(self):
|
def clearData(self):
|
||||||
|
@ -984,9 +1015,11 @@ class BaseNode(BaseObject):
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(self.internalFolder)
|
shutil.rmtree(self.internalFolder)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# We could get some "Device or resource busy" on .nfs file while removing the folder on linux network.
|
# We could get some "Device or resource busy" on .nfs file while removing the folder
|
||||||
# On windows, some output files may be open for visualization and the removal will fail.
|
# on Linux network.
|
||||||
# On both cases, we can ignore it.
|
# On Windows, some output files may be open for visualization and the removal will
|
||||||
|
# fail.
|
||||||
|
# In both cases, we can ignore it.
|
||||||
logging.warning(f"Failed to remove internal folder: '{self.internalFolder}'. Error: {e}.")
|
logging.warning(f"Failed to remove internal folder: '{self.internalFolder}'. Error: {e}.")
|
||||||
self.updateStatusFromCache()
|
self.updateStatusFromCache()
|
||||||
|
|
||||||
|
@ -1011,7 +1044,10 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
@Slot(result=bool)
|
@Slot(result=bool)
|
||||||
def isSubmittedOrRunning(self):
|
def isSubmittedOrRunning(self):
|
||||||
""" Return True if all chunks are at least submitted and there is one running chunk, False otherwise. """
|
"""
|
||||||
|
Return True if all chunks are at least submitted and there is one running chunk,
|
||||||
|
False otherwise.
|
||||||
|
"""
|
||||||
if not self.isAlreadySubmittedOrFinished():
|
if not self.isAlreadySubmittedOrFinished():
|
||||||
return False
|
return False
|
||||||
for chunk in self._chunks:
|
for chunk in self._chunks:
|
||||||
|
@ -1026,7 +1062,10 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
@Slot(result=bool)
|
@Slot(result=bool)
|
||||||
def isFinishedOrRunning(self):
|
def isFinishedOrRunning(self):
|
||||||
""" Return True if all chunks of this Node is either finished or running, False otherwise. """
|
"""
|
||||||
|
Return True if all chunks of this Node is either finished or running, False
|
||||||
|
otherwise.
|
||||||
|
"""
|
||||||
return all(chunk.isFinishedOrRunning() for chunk in self._chunks)
|
return all(chunk.isFinishedOrRunning() for chunk in self._chunks)
|
||||||
|
|
||||||
@Slot(result=bool)
|
@Slot(result=bool)
|
||||||
|
@ -1038,12 +1077,15 @@ class BaseNode(BaseObject):
|
||||||
return [ch for ch in self._chunks if ch.isAlreadySubmitted()]
|
return [ch for ch in self._chunks if ch.isAlreadySubmitted()]
|
||||||
|
|
||||||
def isExtern(self):
|
def isExtern(self):
|
||||||
""" Return True if at least one chunk of this Node has an external execution mode, False otherwise.
|
"""
|
||||||
|
Return True if at least one chunk of this Node has an external execution mode,
|
||||||
|
False otherwise.
|
||||||
|
|
||||||
It is not enough to check whether the first chunk's execution mode is external, because computations
|
It is not enough to check whether the first chunk's execution mode is external,
|
||||||
may have been started locally, interrupted, and restarted externally. In that case, if the first
|
because computations may have been started locally, interrupted, and restarted externally.
|
||||||
chunk has completed locally before the computations were interrupted, its execution mode will always
|
In that case, if the first chunk has completed locally before the computations were
|
||||||
be local, even if computations resume externally.
|
interrupted, its execution mode will always be local, even if computations resume
|
||||||
|
externally.
|
||||||
"""
|
"""
|
||||||
if len(self._chunks) == 0:
|
if len(self._chunks) == 0:
|
||||||
return False
|
return False
|
||||||
|
@ -1051,8 +1093,9 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def clearSubmittedChunks(self):
|
def clearSubmittedChunks(self):
|
||||||
""" Reset all submitted chunks to Status.NONE. This method should be used to clear inconsistent status
|
"""
|
||||||
if a computation failed without informing the graph.
|
Reset all submitted chunks to Status.NONE. This method should be used to clear
|
||||||
|
inconsistent status if a computation failed without informing the graph.
|
||||||
|
|
||||||
Warnings:
|
Warnings:
|
||||||
This must be used with caution. This could lead to inconsistent node status
|
This must be used with caution. This could lead to inconsistent node status
|
||||||
|
@ -1069,9 +1112,7 @@ class BaseNode(BaseObject):
|
||||||
chunk.upgradeStatusTo(Status.NONE, ExecMode.NONE)
|
chunk.upgradeStatusTo(Status.NONE, ExecMode.NONE)
|
||||||
|
|
||||||
def upgradeStatusTo(self, newStatus):
|
def upgradeStatusTo(self, newStatus):
|
||||||
"""
|
""" Upgrade node to the given status and save it on disk. """
|
||||||
Upgrade node to the given status and save it on disk.
|
|
||||||
"""
|
|
||||||
for chunk in self._chunks:
|
for chunk in self._chunks:
|
||||||
chunk.upgradeStatusTo(newStatus)
|
chunk.upgradeStatusTo(newStatus)
|
||||||
|
|
||||||
|
@ -1083,7 +1124,7 @@ class BaseNode(BaseObject):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _getAttributeChangedCallback(self, attr: Attribute) -> Optional[Callable]:
|
def _getAttributeChangedCallback(self, attr: Attribute) -> Optional[Callable]:
|
||||||
"""Get the node descriptor-defined value changed callback associated to `attr` if any."""
|
""" Get the node descriptor-defined value changed callback associated to `attr` if any. """
|
||||||
|
|
||||||
# Callbacks cannot be defined on nested attributes.
|
# Callbacks cannot be defined on nested attributes.
|
||||||
if attr.root is not None:
|
if attr.root is not None:
|
||||||
|
@ -1097,7 +1138,8 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
def _onAttributeChanged(self, attr: Attribute):
|
def _onAttributeChanged(self, attr: Attribute):
|
||||||
"""
|
"""
|
||||||
When an attribute value has changed, a specific function can be defined in the descriptor and be called.
|
When an attribute value has changed, a specific function can be defined in the descriptor
|
||||||
|
and be called.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
attr: The Attribute that has changed.
|
attr: The Attribute that has changed.
|
||||||
|
@ -1132,7 +1174,9 @@ class BaseNode(BaseObject):
|
||||||
edge.dst.valueChanged.emit()
|
edge.dst.valueChanged.emit()
|
||||||
|
|
||||||
def onAttributeClicked(self, attr):
|
def onAttributeClicked(self, attr):
|
||||||
""" When an attribute is clicked, a specific function can be defined in the descriptor and be called.
|
"""
|
||||||
|
When an attribute is clicked, a specific function can be defined in the descriptor
|
||||||
|
and be called.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
attr (Attribute): attribute that has been clicked
|
attr (Attribute): attribute that has been clicked
|
||||||
|
@ -1230,7 +1274,8 @@ class BaseNode(BaseObject):
|
||||||
chunk.process(forceCompute, inCurrentEnv)
|
chunk.process(forceCompute, inCurrentEnv)
|
||||||
|
|
||||||
def postprocess(self):
|
def postprocess(self):
|
||||||
# Invoke the post process on Client Node to execute after the processing on the node is completed
|
# Invoke the post process on Client Node to execute after the processing on the
|
||||||
|
# node is completed
|
||||||
self.nodeDesc.postprocess(self)
|
self.nodeDesc.postprocess(self)
|
||||||
|
|
||||||
def updateOutputAttr(self):
|
def updateOutputAttr(self):
|
||||||
|
@ -1546,8 +1591,8 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
def hasImageOutputAttribute(self) -> bool:
|
def hasImageOutputAttribute(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if at least one attribute has the 'image' semantic (and can thus be loaded in the 2D Viewer),
|
Return True if at least one attribute has the 'image' semantic (and can thus be loaded in
|
||||||
False otherwise.
|
the 2D Viewer), False otherwise.
|
||||||
"""
|
"""
|
||||||
for attr in self._attributes:
|
for attr in self._attributes:
|
||||||
if not attr.enabled or not attr.isOutput:
|
if not attr.enabled or not attr.isOutput:
|
||||||
|
@ -1558,8 +1603,8 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
def hasSequenceOutputAttribute(self) -> bool:
|
def hasSequenceOutputAttribute(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if at least one attribute has the 'sequence' semantic (and can thus be loaded in the 2D Viewer),
|
Return True if at least one attribute has the 'sequence' semantic (and can thus be loaded in
|
||||||
False otherwise.
|
the 2D Viewer), False otherwise.
|
||||||
"""
|
"""
|
||||||
for attr in self._attributes:
|
for attr in self._attributes:
|
||||||
if not attr.enabled or not attr.isOutput:
|
if not attr.enabled or not attr.isOutput:
|
||||||
|
@ -1570,7 +1615,8 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
def has3DOutputAttribute(self):
|
def has3DOutputAttribute(self):
|
||||||
"""
|
"""
|
||||||
Return True if at least one attribute is a File that can be loaded in the 3D Viewer, False otherwise.
|
Return True if at least one attribute is a File that can be loaded in the 3D Viewer,
|
||||||
|
False otherwise.
|
||||||
"""
|
"""
|
||||||
# List of supported extensions, taken from Viewer3DSettings
|
# List of supported extensions, taken from Viewer3DSettings
|
||||||
supportedExts = ['.obj', '.stl', '.fbx', '.gltf', '.abc', '.ply']
|
supportedExts = ['.obj', '.stl', '.fbx', '.gltf', '.abc', '.ply']
|
||||||
|
@ -1654,14 +1700,16 @@ class Node(BaseNode):
|
||||||
self._sourceCodeFolder = self.nodeDesc.sourceCodeFolder
|
self._sourceCodeFolder = self.nodeDesc.sourceCodeFolder
|
||||||
|
|
||||||
for attrDesc in self.nodeDesc.inputs:
|
for attrDesc in self.nodeDesc.inputs:
|
||||||
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=False, node=self))
|
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None),
|
||||||
|
isOutput=False, node=self))
|
||||||
|
|
||||||
for attrDesc in self.nodeDesc.outputs:
|
for attrDesc in self.nodeDesc.outputs:
|
||||||
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=True, node=self))
|
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None),
|
||||||
|
isOutput=True, node=self))
|
||||||
|
|
||||||
for attrDesc in self.nodeDesc.internalInputs:
|
for attrDesc in self.nodeDesc.internalInputs:
|
||||||
self._internalAttributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=False,
|
self._internalAttributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None),
|
||||||
node=self))
|
isOutput=False, node=self))
|
||||||
|
|
||||||
# Declare events for specific output attributes
|
# Declare events for specific output attributes
|
||||||
for attr in self._attributes:
|
for attr in self._attributes:
|
||||||
|
@ -1891,7 +1939,8 @@ class CompatibilityNode(BaseNode):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def attributeDescFromName(refAttributes, name, value, strict=True):
|
def attributeDescFromName(refAttributes, name, value, strict=True):
|
||||||
"""
|
"""
|
||||||
Try to find a matching attribute description in refAttributes for given attribute 'name' and 'value'.
|
Try to find a matching attribute description in refAttributes for given attribute
|
||||||
|
'name' and 'value'.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
refAttributes ([desc.Attribute]): reference Attributes to look for a description
|
refAttributes ([desc.Attribute]): reference Attributes to look for a description
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue