[core] New dynamic output attributes

This commit is contained in:
Fabien Castan 2024-06-10 19:56:18 +02:00
parent 9a09310f07
commit d5e356c0aa
6 changed files with 139 additions and 14 deletions

View file

@ -686,6 +686,10 @@ class BaseNode(BaseObject):
def minDepth(self):
return self.graph.getDepth(self, minimal=True)
@property
def valuesFile(self):
return os.path.join(self.graph.cacheDir, self.internalFolder, 'values')
def getInputNodes(self, recursive, dependenciesOnly):
return self.graph.getInputNodes(self, recursive=recursive, dependenciesOnly=dependenciesOnly)
@ -955,6 +959,7 @@ class BaseNode(BaseObject):
}
self._computeUids()
self._buildCmdVars()
self.updateOutputAttr()
if self.nodeDesc:
self.nodeDesc.postUpdate(self)
# Notify internal folder change if needed
@ -972,8 +977,11 @@ class BaseNode(BaseObject):
"""
Update node status based on status file content/existence.
"""
s = self.globalStatus
for chunk in self._chunks:
chunk.updateStatusFromCache()
# logging.warning("updateStatusFromCache: {}, status: {} => {}".format(self.name, s, self.globalStatus))
self.updateOutputAttr()
def submit(self, forceCompute=False):
for chunk in self._chunks:
@ -988,10 +996,71 @@ class BaseNode(BaseObject):
def processIteration(self, iteration):
self._chunks[iteration].process()
def preprocess(self):
pass
def process(self, forceCompute=False):
for chunk in self._chunks:
chunk.process(forceCompute)
def postprocess(self):
self.saveOutputAttr()
def updateOutputAttr(self):
if not self.nodeDesc:
return
if not self.nodeDesc.hasDynamicOutputAttribute:
return
# logging.warning("updateOutputAttr: {}, status: {}".format(self.name, self.globalStatus))
if self.getGlobalStatus() == Status.SUCCESS:
self.loadOutputAttr()
else:
self.resetOutputAttr()
def resetOutputAttr(self):
if not self.nodeDesc.hasDynamicOutputAttribute:
return
# logging.warning("resetOutputAttr: {}".format(self.name))
for output in self.nodeDesc.outputs:
if output.isDynamicValue:
self.attribute(output.name).value = None
def loadOutputAttr(self):
""" Load output attributes with dynamic values from a values.json file.
"""
if not self.nodeDesc.hasDynamicOutputAttribute:
return
valuesFile = self.valuesFile
if not os.path.exists(valuesFile):
logging.warning("No output attr file: {}".format(valuesFile))
return
# logging.warning("load output attr: {}, value: {}".format(self.name, valuesFile))
with open(valuesFile, 'r') as jsonFile:
data = json.load(jsonFile)
# logging.warning(data)
for output in self.nodeDesc.outputs:
if output.isDynamicValue:
self.attribute(output.name).value = data[output.name]
def saveOutputAttr(self):
""" Save output attributes with dynamic values into a values.json file.
"""
if not self.nodeDesc.hasDynamicOutputAttribute:
return
data = {}
for output in self.nodeDesc.outputs:
if output.isDynamicValue:
data[output.name] = self.attribute(output.name).value
valuesFile = self.valuesFile
# logging.warning("save output attr: {}, value: {}".format(self.name, valuesFile))
valuesFilepathWriting = getWritingFilepath(valuesFile)
with open(valuesFilepathWriting, 'w') as jsonFile:
json.dump(data, jsonFile, indent=4)
renameWritingToFinalPath(valuesFilepathWriting, valuesFile)
def endSequence(self):
pass
@ -1227,6 +1296,7 @@ class BaseNode(BaseObject):
comment = Property(str, getComment, notify=internalAttributesChanged)
internalFolderChanged = Signal()
internalFolder = Property(str, internalFolder.fget, notify=internalFolderChanged)
valuesFile = Property(str, valuesFile.fget, notify=internalFolderChanged)
depthChanged = Signal()
depth = Property(int, depth.fget, notify=depthChanged)
minDepth = Property(int, minDepth.fget, notify=depthChanged)
@ -1281,12 +1351,19 @@ class Node(BaseNode):
for attrDesc in self.nodeDesc.internalInputs:
self._internalAttributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=False, node=self))
# List attributes per uid
# Declare events for specific output attributes
for attr in self._attributes:
if attr.isOutput and attr.desc.semantic == "image":
attr.enabledChanged.connect(self.outputAttrEnabledChanged)
for uidIndex in attr.attributeDesc.uid:
self.attributesPerUid[uidIndex].add(attr)
# List attributes per uid
for attr in self._attributes:
if attr.isInput:
for uidIndex in attr.attributeDesc.uid:
self.attributesPerUid[uidIndex].add(attr)
else:
if attr.attributeDesc.uid:
logging.error(f"Output Attribute should not contain a UID: '{nodeType}.{attr.name}'")
# Add internal attributes with a UID to the list
for attr in self._internalAttributes:
@ -1347,7 +1424,7 @@ class Node(BaseNode):
def toDict(self):
inputs = {k: v.getExportValue() for k, v in self._attributes.objects.items() if v.isInput}
internalInputs = {k: v.getExportValue() for k, v in self._internalAttributes.objects.items()}
outputs = ({k: v.getExportValue() for k, v in self._attributes.objects.items() if v.isOutput})
outputs = ({k: v.getExportValue() for k, v in self._attributes.objects.items() if v.isOutput and not v.desc.isDynamicValue})
return {
'nodeType': self.nodeType,
@ -1714,7 +1791,7 @@ def nodeFactory(nodeDict, name=None, template=False, uidConflict=False):
# raising compatibility issues if their number differs: in that case, it is only useful
# if some internal attributes do not exist or are invalid
if not template and (sorted([attr.name for attr in nodeDesc.inputs if not isinstance(attr, desc.PushButtonParam)]) != sorted(inputs.keys()) or \
sorted([attr.name for attr in nodeDesc.outputs]) != sorted(outputs.keys())):
sorted([attr.name for attr in nodeDesc.outputs if not attr.isDynamicValue]) != sorted(outputs.keys())):
compatibilityIssue = CompatibilityIssue.DescriptionConflict
# check whether there are any internal attributes that are invalidating in the node description: if there