mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-29 18:27:23 +02:00
Merge pull request #1744 from alicevision/dev/internalAttributes
Add internal attributes in "Notes" tab
This commit is contained in:
commit
ce2085faad
10 changed files with 471 additions and 25 deletions
|
@ -143,6 +143,10 @@ class Attribute(BaseObject):
|
||||||
self._enabled = v
|
self._enabled = v
|
||||||
self.enabledChanged.emit()
|
self.enabledChanged.emit()
|
||||||
|
|
||||||
|
def getUidIgnoreValue(self):
|
||||||
|
""" Value for which the attribute should be ignored during the UID computation. """
|
||||||
|
return self.attributeDesc.uidIgnoreValue
|
||||||
|
|
||||||
def _get_value(self):
|
def _get_value(self):
|
||||||
if self.isLink:
|
if self.isLink:
|
||||||
return self.getLinkParam().value
|
return self.getLinkParam().value
|
||||||
|
@ -168,6 +172,10 @@ class Attribute(BaseObject):
|
||||||
# TODO: only update the graph if this attribute participates to a UID
|
# TODO: only update the graph if this attribute participates to a UID
|
||||||
if self.isInput:
|
if self.isInput:
|
||||||
self.requestGraphUpdate()
|
self.requestGraphUpdate()
|
||||||
|
# TODO: only call update of the node if the attribute is internal
|
||||||
|
# Internal attributes are set as inputs
|
||||||
|
self.requestNodeUpdate()
|
||||||
|
|
||||||
self.valueChanged.emit()
|
self.valueChanged.emit()
|
||||||
|
|
||||||
def upgradeValue(self, exportedValue):
|
def upgradeValue(self, exportedValue):
|
||||||
|
@ -181,6 +189,12 @@ class Attribute(BaseObject):
|
||||||
self.node.graph.markNodesDirty(self.node)
|
self.node.graph.markNodesDirty(self.node)
|
||||||
self.node.graph.update()
|
self.node.graph.update()
|
||||||
|
|
||||||
|
def requestNodeUpdate(self):
|
||||||
|
# Update specific node information that do not affect the rest of the graph
|
||||||
|
# (like internal attributes)
|
||||||
|
if self.node:
|
||||||
|
self.node.updateInternalAttributes()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isOutput(self):
|
def isOutput(self):
|
||||||
return self._isOutput
|
return self._isOutput
|
||||||
|
@ -323,6 +337,7 @@ class Attribute(BaseObject):
|
||||||
node = Property(BaseObject, node.fget, constant=True)
|
node = Property(BaseObject, node.fget, constant=True)
|
||||||
enabledChanged = Signal()
|
enabledChanged = Signal()
|
||||||
enabled = Property(bool, getEnabled, setEnabled, notify=enabledChanged)
|
enabled = Property(bool, getEnabled, setEnabled, notify=enabledChanged)
|
||||||
|
uidIgnoreValue = Property(Variant, getUidIgnoreValue, constant=True)
|
||||||
|
|
||||||
|
|
||||||
def raiseIfLink(func):
|
def raiseIfLink(func):
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Attribute(BaseObject):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, label, description, value, advanced, semantic, uid, group, enabled):
|
def __init__(self, name, label, description, value, advanced, semantic, uid, group, enabled, uidIgnoreValue=None):
|
||||||
super(Attribute, self).__init__()
|
super(Attribute, self).__init__()
|
||||||
self._name = name
|
self._name = name
|
||||||
self._label = label
|
self._label = label
|
||||||
|
@ -25,6 +25,7 @@ class Attribute(BaseObject):
|
||||||
self._advanced = advanced
|
self._advanced = advanced
|
||||||
self._enabled = enabled
|
self._enabled = enabled
|
||||||
self._semantic = semantic
|
self._semantic = semantic
|
||||||
|
self._uidIgnoreValue = uidIgnoreValue
|
||||||
|
|
||||||
name = Property(str, lambda self: self._name, constant=True)
|
name = Property(str, lambda self: self._name, constant=True)
|
||||||
label = Property(str, lambda self: self._label, constant=True)
|
label = Property(str, lambda self: self._label, constant=True)
|
||||||
|
@ -35,6 +36,7 @@ class Attribute(BaseObject):
|
||||||
advanced = Property(bool, lambda self: self._advanced, constant=True)
|
advanced = Property(bool, lambda self: self._advanced, constant=True)
|
||||||
enabled = Property(Variant, lambda self: self._enabled, constant=True)
|
enabled = Property(Variant, lambda self: self._enabled, constant=True)
|
||||||
semantic = Property(str, lambda self: self._semantic, constant=True)
|
semantic = Property(str, lambda self: self._semantic, constant=True)
|
||||||
|
uidIgnoreValue = Property(Variant, lambda self: self._uidIgnoreValue, constant=True)
|
||||||
type = Property(str, lambda self: self.__class__.__name__, constant=True)
|
type = Property(str, lambda self: self.__class__.__name__, constant=True)
|
||||||
|
|
||||||
def validateValue(self, value):
|
def validateValue(self, value):
|
||||||
|
@ -201,8 +203,9 @@ class GroupAttribute(Attribute):
|
||||||
class Param(Attribute):
|
class Param(Attribute):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, label, description, value, uid, group, advanced, semantic, enabled):
|
def __init__(self, name, label, description, value, uid, group, advanced, semantic, enabled, uidIgnoreValue=None):
|
||||||
super(Param, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
|
super(Param, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
|
||||||
|
uidIgnoreValue=uidIgnoreValue)
|
||||||
|
|
||||||
|
|
||||||
class File(Attribute):
|
class File(Attribute):
|
||||||
|
@ -329,8 +332,9 @@ class ChoiceParam(Param):
|
||||||
class StringParam(Param):
|
class StringParam(Param):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True):
|
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True, uidIgnoreValue=None):
|
||||||
super(StringParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
|
super(StringParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
|
||||||
|
uidIgnoreValue=uidIgnoreValue)
|
||||||
|
|
||||||
def validateValue(self, value):
|
def validateValue(self, value):
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
|
@ -343,6 +347,19 @@ class StringParam(Param):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class ColorParam(Param):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True):
|
||||||
|
super(ColorParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
|
||||||
|
|
||||||
|
def validateValue(self, value):
|
||||||
|
if not isinstance(value, str) or len(value.split(" ")) > 1:
|
||||||
|
raise ValueError('ColorParam value should be a string containing either an SVG name or an hexadecimal '
|
||||||
|
'color code (param: {}, value: {}, type: {})'.format(self.name, value, type(value)))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Level(Enum):
|
class Level(Enum):
|
||||||
NONE = 0
|
NONE = 0
|
||||||
NORMAL = 1
|
NORMAL = 1
|
||||||
|
@ -485,6 +502,43 @@ class Node(object):
|
||||||
ram = Level.NORMAL
|
ram = Level.NORMAL
|
||||||
packageName = ''
|
packageName = ''
|
||||||
packageVersion = ''
|
packageVersion = ''
|
||||||
|
internalInputs = [
|
||||||
|
StringParam(
|
||||||
|
name="invalidation",
|
||||||
|
label="Invalidation Message",
|
||||||
|
description="A message that will invalidate the node's output folder.\n"
|
||||||
|
"This is useful for development, we can invalidate the output of the node when we modify the code.\n"
|
||||||
|
"It is displayed in bold font in the invalidation/comment messages tooltip.",
|
||||||
|
value="",
|
||||||
|
semantic="multiline",
|
||||||
|
uid=[0],
|
||||||
|
advanced=True,
|
||||||
|
uidIgnoreValue="", # If the invalidation string is empty, it does not participate to the node's UID
|
||||||
|
),
|
||||||
|
StringParam(
|
||||||
|
name="comment",
|
||||||
|
label="Comments",
|
||||||
|
description="User comments describing this specific node instance.\n"
|
||||||
|
"It is displayed in regular font in the invalidation/comment messages tooltip.",
|
||||||
|
value="",
|
||||||
|
semantic="multiline",
|
||||||
|
uid=[],
|
||||||
|
),
|
||||||
|
StringParam(
|
||||||
|
name="label",
|
||||||
|
label="Node's Label",
|
||||||
|
description="Customize the default label (to replace the technical name of the node instance).",
|
||||||
|
value="",
|
||||||
|
uid=[],
|
||||||
|
),
|
||||||
|
ColorParam(
|
||||||
|
name="color",
|
||||||
|
label="Color",
|
||||||
|
description="Custom color for the node (SVG name or hexadecimal code).",
|
||||||
|
value="",
|
||||||
|
uid=[],
|
||||||
|
)
|
||||||
|
]
|
||||||
inputs = []
|
inputs = []
|
||||||
outputs = []
|
outputs = []
|
||||||
size = StaticNodeSize(1)
|
size = StaticNodeSize(1)
|
||||||
|
|
|
@ -695,9 +695,24 @@ class Graph(BaseObject):
|
||||||
# type: (str) -> Attribute
|
# type: (str) -> Attribute
|
||||||
"""
|
"""
|
||||||
Return the attribute identified by the unique name 'fullName'.
|
Return the attribute identified by the unique name 'fullName'.
|
||||||
|
If it does not exist, return None.
|
||||||
"""
|
"""
|
||||||
node, attribute = fullName.split('.', 1)
|
node, attribute = fullName.split('.', 1)
|
||||||
return self.node(node).attribute(attribute)
|
if self.node(node).hasAttribute(attribute):
|
||||||
|
return self.node(node).attribute(attribute)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Slot(str, result=Attribute)
|
||||||
|
def internalAttribute(self, fullName):
|
||||||
|
# type: (str) -> Attribute
|
||||||
|
"""
|
||||||
|
Return the internal attribute identified by the unique name 'fullName'.
|
||||||
|
If it does not exist, return None.
|
||||||
|
"""
|
||||||
|
node, attribute = fullName.split('.', 1)
|
||||||
|
if self.node(node).hasInternalAttribute(attribute):
|
||||||
|
return self.node(node).internalAttribute(attribute)
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getNodeIndexFromName(name):
|
def getNodeIndexFromName(name):
|
||||||
|
@ -1229,14 +1244,14 @@ class Graph(BaseObject):
|
||||||
|
|
||||||
def getNonDefaultInputAttributes(self):
|
def getNonDefaultInputAttributes(self):
|
||||||
"""
|
"""
|
||||||
Instead of getting all the inputs attribute keys, only get the keys of
|
Instead of getting all the inputs and internal attribute keys, only get the keys of
|
||||||
the attributes whose value is not the default one.
|
the attributes whose value is not the default one.
|
||||||
The output attributes, UIDs, parallelization parameters and internal folder are
|
The output attributes, UIDs, parallelization parameters and internal folder are
|
||||||
not relevant for templates, so they are explicitly removed from the returned dictionary.
|
not relevant for templates, so they are explicitly removed from the returned dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: self.toDict() with the output attributes, UIDs, parallelization parameters, internal folder
|
dict: self.toDict() with the output attributes, UIDs, parallelization parameters, internal folder
|
||||||
and input attributes with default values removed
|
and input/internal attributes with default values removed
|
||||||
"""
|
"""
|
||||||
graph = self.toDict()
|
graph = self.toDict()
|
||||||
for nodeName in graph.keys():
|
for nodeName in graph.keys():
|
||||||
|
@ -1244,12 +1259,27 @@ class Graph(BaseObject):
|
||||||
|
|
||||||
inputKeys = list(graph[nodeName]["inputs"].keys())
|
inputKeys = list(graph[nodeName]["inputs"].keys())
|
||||||
|
|
||||||
|
internalInputKeys = []
|
||||||
|
internalInputs = graph[nodeName].get("internalInputs", None)
|
||||||
|
if internalInputs:
|
||||||
|
internalInputKeys = list(internalInputs.keys())
|
||||||
|
|
||||||
for attrName in inputKeys:
|
for attrName in inputKeys:
|
||||||
attribute = node.attribute(attrName)
|
attribute = node.attribute(attrName)
|
||||||
# check that attribute is not a link for choice attributes
|
# check that attribute is not a link for choice attributes
|
||||||
if attribute.isDefault and not attribute.isLink:
|
if attribute.isDefault and not attribute.isLink:
|
||||||
del graph[nodeName]["inputs"][attrName]
|
del graph[nodeName]["inputs"][attrName]
|
||||||
|
|
||||||
|
for attrName in internalInputKeys:
|
||||||
|
attribute = node.internalAttribute(attrName)
|
||||||
|
# check that internal attribute is not a link for choice attributes
|
||||||
|
if attribute.isDefault and not attribute.isLink:
|
||||||
|
del graph[nodeName]["internalInputs"][attrName]
|
||||||
|
|
||||||
|
# If all the internal attributes are set to their default values, remove the entry
|
||||||
|
if len(graph[nodeName]["internalInputs"]) == 0:
|
||||||
|
del graph[nodeName]["internalInputs"]
|
||||||
|
|
||||||
del graph[nodeName]["outputs"]
|
del graph[nodeName]["outputs"]
|
||||||
del graph[nodeName]["uids"]
|
del graph[nodeName]["uids"]
|
||||||
del graph[nodeName]["internalFolder"]
|
del graph[nodeName]["internalFolder"]
|
||||||
|
|
|
@ -499,6 +499,7 @@ class BaseNode(BaseObject):
|
||||||
self._size = 0
|
self._size = 0
|
||||||
self._position = position or Position()
|
self._position = position or Position()
|
||||||
self._attributes = DictModel(keyAttrName='name', parent=self)
|
self._attributes = DictModel(keyAttrName='name', parent=self)
|
||||||
|
self._internalAttributes = DictModel(keyAttrName='name', parent=self)
|
||||||
self.attributesPerUid = defaultdict(set)
|
self.attributesPerUid = defaultdict(set)
|
||||||
self._alive = True # for QML side to know if the node can be used or is going to be deleted
|
self._alive = True # for QML side to know if the node can be used or is going to be deleted
|
||||||
self._locked = False
|
self._locked = False
|
||||||
|
@ -523,10 +524,41 @@ class BaseNode(BaseObject):
|
||||||
def getLabel(self):
|
def getLabel(self):
|
||||||
"""
|
"""
|
||||||
Returns:
|
Returns:
|
||||||
str: the high-level label of this node
|
str: the user-provided label if it exists, the high-level label of this node otherwise
|
||||||
"""
|
"""
|
||||||
|
if self.hasInternalAttribute("label"):
|
||||||
|
label = self.internalAttribute("label").value.strip()
|
||||||
|
if label:
|
||||||
|
return label
|
||||||
return self.nameToLabel(self._name)
|
return self.nameToLabel(self._name)
|
||||||
|
|
||||||
|
def getColor(self):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
str: the user-provided custom color of the node if it exists, empty string otherwise
|
||||||
|
"""
|
||||||
|
if self.hasInternalAttribute("color"):
|
||||||
|
return self.internalAttribute("color").value.strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def getInvalidationMessage(self):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
str: the invalidation message on the node if it exists, empty string otherwise
|
||||||
|
"""
|
||||||
|
if self.hasInternalAttribute("invalidation"):
|
||||||
|
return self.internalAttribute("invalidation").value
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def getComment(self):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
str: the comments on the node if they exist, empty string otherwise
|
||||||
|
"""
|
||||||
|
if self.hasInternalAttribute("comment"):
|
||||||
|
return self.internalAttribute("comment").value
|
||||||
|
return ""
|
||||||
|
|
||||||
@Slot(str, result=str)
|
@Slot(str, result=str)
|
||||||
def nameToLabel(self, name):
|
def nameToLabel(self, name):
|
||||||
"""
|
"""
|
||||||
|
@ -568,13 +600,37 @@ class BaseNode(BaseObject):
|
||||||
att = self._attributes.get(name)
|
att = self._attributes.get(name)
|
||||||
return att
|
return att
|
||||||
|
|
||||||
|
@Slot(str, result=Attribute)
|
||||||
|
def internalAttribute(self, name):
|
||||||
|
# No group or list attributes for internal attributes
|
||||||
|
# The internal attribute itself can be returned directly
|
||||||
|
return self._internalAttributes.get(name)
|
||||||
|
|
||||||
|
def setInternalAttributeValues(self, values):
|
||||||
|
# initialize internal attribute values
|
||||||
|
for k, v in values.items():
|
||||||
|
attr = self.internalAttribute(k)
|
||||||
|
attr.value = v
|
||||||
|
|
||||||
def getAttributes(self):
|
def getAttributes(self):
|
||||||
return self._attributes
|
return self._attributes
|
||||||
|
|
||||||
|
def getInternalAttributes(self):
|
||||||
|
return self._internalAttributes
|
||||||
|
|
||||||
@Slot(str, result=bool)
|
@Slot(str, result=bool)
|
||||||
def hasAttribute(self, name):
|
def hasAttribute(self, name):
|
||||||
|
# Complex name indicating group or list attribute: parse it and get the
|
||||||
|
# first output element to check for the attribute's existence
|
||||||
|
if "[" in name or "." in name:
|
||||||
|
p = self.attributeRE.findall(name)
|
||||||
|
return p[0][0] in self._attributes.keys() or p[0][1] in self._attributes.keys()
|
||||||
return name in self._attributes.keys()
|
return name in self._attributes.keys()
|
||||||
|
|
||||||
|
@Slot(str, result=bool)
|
||||||
|
def hasInternalAttribute(self, name):
|
||||||
|
return name in self._internalAttributes.keys()
|
||||||
|
|
||||||
def _applyExpr(self):
|
def _applyExpr(self):
|
||||||
for attr in self._attributes:
|
for attr in self._attributes:
|
||||||
attr._applyExpr()
|
attr._applyExpr()
|
||||||
|
@ -632,7 +688,7 @@ class BaseNode(BaseObject):
|
||||||
""" Compute node uids by combining associated attributes' uids. """
|
""" Compute node uids by combining associated attributes' uids. """
|
||||||
for uidIndex, associatedAttributes in self.attributesPerUid.items():
|
for uidIndex, associatedAttributes in self.attributesPerUid.items():
|
||||||
# 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 = [(a.getName(), a.uid(uidIndex)) for a in associatedAttributes if a.enabled]
|
uidAttributes = [(a.getName(), a.uid(uidIndex)) for a in associatedAttributes if a.enabled and a.value != a.uidIgnoreValue]
|
||||||
uidAttributes.sort()
|
uidAttributes.sort()
|
||||||
self._uids[uidIndex] = hashValue(uidAttributes)
|
self._uids[uidIndex] = hashValue(uidAttributes)
|
||||||
|
|
||||||
|
@ -842,6 +898,9 @@ class BaseNode(BaseObject):
|
||||||
if self.internalFolder != folder:
|
if self.internalFolder != folder:
|
||||||
self.internalFolderChanged.emit()
|
self.internalFolderChanged.emit()
|
||||||
|
|
||||||
|
def updateInternalAttributes(self):
|
||||||
|
self.internalAttributesChanged.emit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def internalFolder(self):
|
def internalFolder(self):
|
||||||
return self._internalFolder.format(**self._cmdVars)
|
return self._internalFolder.format(**self._cmdVars)
|
||||||
|
@ -1066,7 +1125,6 @@ class BaseNode(BaseObject):
|
||||||
|
|
||||||
|
|
||||||
name = Property(str, getName, constant=True)
|
name = Property(str, getName, constant=True)
|
||||||
label = Property(str, getLabel, constant=True)
|
|
||||||
nodeType = Property(str, nodeType.fget, constant=True)
|
nodeType = Property(str, nodeType.fget, constant=True)
|
||||||
documentation = Property(str, getDocumentation, constant=True)
|
documentation = Property(str, getDocumentation, constant=True)
|
||||||
positionChanged = Signal()
|
positionChanged = Signal()
|
||||||
|
@ -1074,6 +1132,12 @@ class BaseNode(BaseObject):
|
||||||
x = Property(float, lambda self: self._position.x, notify=positionChanged)
|
x = Property(float, lambda self: self._position.x, notify=positionChanged)
|
||||||
y = Property(float, lambda self: self._position.y, notify=positionChanged)
|
y = Property(float, lambda self: self._position.y, notify=positionChanged)
|
||||||
attributes = Property(BaseObject, getAttributes, constant=True)
|
attributes = Property(BaseObject, getAttributes, constant=True)
|
||||||
|
internalAttributes = Property(BaseObject, getInternalAttributes, constant=True)
|
||||||
|
internalAttributesChanged = Signal()
|
||||||
|
label = Property(str, getLabel, notify=internalAttributesChanged)
|
||||||
|
color = Property(str, getColor, notify=internalAttributesChanged)
|
||||||
|
invalidation = Property(str, getInvalidationMessage, notify=internalAttributesChanged)
|
||||||
|
comment = Property(str, getComment, notify=internalAttributesChanged)
|
||||||
internalFolderChanged = Signal()
|
internalFolderChanged = Signal()
|
||||||
internalFolder = Property(str, internalFolder.fget, notify=internalFolderChanged)
|
internalFolder = Property(str, internalFolder.fget, notify=internalFolderChanged)
|
||||||
depthChanged = Signal()
|
depthChanged = Signal()
|
||||||
|
@ -1123,11 +1187,19 @@ class Node(BaseNode):
|
||||||
for attrDesc in self.nodeDesc.outputs:
|
for attrDesc in self.nodeDesc.outputs:
|
||||||
self._attributes.add(attributeFactory(attrDesc, None, True, self))
|
self._attributes.add(attributeFactory(attrDesc, None, True, self))
|
||||||
|
|
||||||
|
for attrDesc in self.nodeDesc.internalInputs:
|
||||||
|
self._internalAttributes.add(attributeFactory(attrDesc, None, False, self))
|
||||||
|
|
||||||
# List attributes per uid
|
# List attributes per uid
|
||||||
for attr in self._attributes:
|
for attr in self._attributes:
|
||||||
for uidIndex in attr.attributeDesc.uid:
|
for uidIndex in attr.attributeDesc.uid:
|
||||||
self.attributesPerUid[uidIndex].add(attr)
|
self.attributesPerUid[uidIndex].add(attr)
|
||||||
|
|
||||||
|
# Add internal attributes with a UID to the list
|
||||||
|
for attr in self._internalAttributes:
|
||||||
|
for uidIndex in attr.attributeDesc.uid:
|
||||||
|
self.attributesPerUid[uidIndex].add(attr)
|
||||||
|
|
||||||
self.setAttributeValues(kwargs)
|
self.setAttributeValues(kwargs)
|
||||||
|
|
||||||
def setAttributeValues(self, values):
|
def setAttributeValues(self, values):
|
||||||
|
@ -1150,8 +1222,22 @@ class Node(BaseNode):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def upgradeInternalAttributeValues(self, values):
|
||||||
|
# initialize internal attibute values
|
||||||
|
for k, v in values.items():
|
||||||
|
if not self.hasInternalAttribute(k):
|
||||||
|
# skip missing atributes
|
||||||
|
continue
|
||||||
|
attr = self.internalAttribute(k)
|
||||||
|
if attr.isInput:
|
||||||
|
try:
|
||||||
|
attr.upgradeValue(v)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
def toDict(self):
|
def toDict(self):
|
||||||
inputs = {k: v.getExportValue() for k, v in self._attributes.objects.items() if v.isInput}
|
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})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1165,6 +1251,7 @@ class Node(BaseNode):
|
||||||
'uids': self._uids,
|
'uids': self._uids,
|
||||||
'internalFolder': self._internalFolder,
|
'internalFolder': self._internalFolder,
|
||||||
'inputs': {k: v for k, v in inputs.items() if v is not None}, # filter empty values
|
'inputs': {k: v for k, v in inputs.items() if v is not None}, # filter empty values
|
||||||
|
'internalInputs': {k: v for k, v in internalInputs.items() if v is not None},
|
||||||
'outputs': outputs,
|
'outputs': outputs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1220,6 +1307,7 @@ class CompatibilityNode(BaseNode):
|
||||||
self.version = Version(self.nodeDict.get("version", None))
|
self.version = Version(self.nodeDict.get("version", None))
|
||||||
|
|
||||||
self._inputs = self.nodeDict.get("inputs", {})
|
self._inputs = self.nodeDict.get("inputs", {})
|
||||||
|
self._internalInputs = self.nodeDict.get("internalInputs", {})
|
||||||
self.outputs = self.nodeDict.get("outputs", {})
|
self.outputs = self.nodeDict.get("outputs", {})
|
||||||
self._internalFolder = self.nodeDict.get("internalFolder", "")
|
self._internalFolder = self.nodeDict.get("internalFolder", "")
|
||||||
self._uids = self.nodeDict.get("uids", {})
|
self._uids = self.nodeDict.get("uids", {})
|
||||||
|
@ -1231,11 +1319,15 @@ class CompatibilityNode(BaseNode):
|
||||||
|
|
||||||
# create input attributes
|
# create input attributes
|
||||||
for attrName, value in self._inputs.items():
|
for attrName, value in self._inputs.items():
|
||||||
self._addAttribute(attrName, value, False)
|
self._addAttribute(attrName, value, isOutput=False)
|
||||||
|
|
||||||
# create outputs attributes
|
# create outputs attributes
|
||||||
for attrName, value in self.outputs.items():
|
for attrName, value in self.outputs.items():
|
||||||
self._addAttribute(attrName, value, True)
|
self._addAttribute(attrName, value, isOutput=True)
|
||||||
|
|
||||||
|
# create internal attributes
|
||||||
|
for attrName, value in self._internalInputs.items():
|
||||||
|
self._addAttribute(attrName, value, isOutput=False, internalAttr=True)
|
||||||
|
|
||||||
# create NodeChunks matching serialized parallelization settings
|
# create NodeChunks matching serialized parallelization settings
|
||||||
self._chunks.setObjectList([
|
self._chunks.setObjectList([
|
||||||
|
@ -1328,7 +1420,7 @@ class CompatibilityNode(BaseNode):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _addAttribute(self, name, val, isOutput):
|
def _addAttribute(self, name, val, isOutput, internalAttr=False):
|
||||||
"""
|
"""
|
||||||
Add a new attribute on this node.
|
Add a new attribute on this node.
|
||||||
|
|
||||||
|
@ -1336,19 +1428,26 @@ class CompatibilityNode(BaseNode):
|
||||||
name (str): the name of the attribute
|
name (str): the name of the attribute
|
||||||
val: the attribute's value
|
val: the attribute's value
|
||||||
isOutput: whether the attribute is an output
|
isOutput: whether the attribute is an output
|
||||||
|
internalAttr: whether the attribute is internal
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: whether the attribute exists in the node description
|
bool: whether the attribute exists in the node description
|
||||||
"""
|
"""
|
||||||
attrDesc = None
|
attrDesc = None
|
||||||
if self.nodeDesc:
|
if self.nodeDesc:
|
||||||
refAttrs = self.nodeDesc.outputs if isOutput else self.nodeDesc.inputs
|
if internalAttr:
|
||||||
|
refAttrs = self.nodeDesc.internalInputs
|
||||||
|
else:
|
||||||
|
refAttrs = self.nodeDesc.outputs if isOutput else self.nodeDesc.inputs
|
||||||
attrDesc = CompatibilityNode.attributeDescFromName(refAttrs, name, val)
|
attrDesc = CompatibilityNode.attributeDescFromName(refAttrs, name, val)
|
||||||
matchDesc = attrDesc is not None
|
matchDesc = attrDesc is not None
|
||||||
if not matchDesc:
|
if attrDesc is None:
|
||||||
attrDesc = CompatibilityNode.attributeDescFromValue(name, val, isOutput)
|
attrDesc = CompatibilityNode.attributeDescFromValue(name, val, isOutput)
|
||||||
attribute = attributeFactory(attrDesc, val, isOutput, self)
|
attribute = attributeFactory(attrDesc, val, isOutput, self)
|
||||||
self._attributes.add(attribute)
|
if internalAttr:
|
||||||
|
self._internalAttributes.add(attribute)
|
||||||
|
else:
|
||||||
|
self._attributes.add(attribute)
|
||||||
return matchDesc
|
return matchDesc
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1373,6 +1472,13 @@ class CompatibilityNode(BaseNode):
|
||||||
return self._inputs
|
return self._inputs
|
||||||
return {k: v.getExportValue() for k, v in self._attributes.objects.items() if v.isInput}
|
return {k: v.getExportValue() for k, v in self._attributes.objects.items() if v.isInput}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def internalInputs(self):
|
||||||
|
""" Get current node's internal attributes """
|
||||||
|
if not self.graph:
|
||||||
|
return self._internalInputs
|
||||||
|
return {k: v.getExportValue() for k, v in self._internalAttributes.objects.items()}
|
||||||
|
|
||||||
def toDict(self):
|
def toDict(self):
|
||||||
"""
|
"""
|
||||||
Return the original serialized node that generated a compatibility issue.
|
Return the original serialized node that generated a compatibility issue.
|
||||||
|
@ -1406,9 +1512,16 @@ class CompatibilityNode(BaseNode):
|
||||||
# store attributes that could be used during node upgrade
|
# store attributes that could be used during node upgrade
|
||||||
commonInputs.append(attrName)
|
commonInputs.append(attrName)
|
||||||
|
|
||||||
|
commonInternalAttributes = []
|
||||||
|
for attrName, value in self._internalInputs.items():
|
||||||
|
if self.attributeDescFromName(self.nodeDesc.internalInputs, attrName, value, strict=False):
|
||||||
|
# store internal attributes that could be used during node upgrade
|
||||||
|
commonInternalAttributes.append(attrName)
|
||||||
|
|
||||||
node = Node(self.nodeType, position=self.position)
|
node = Node(self.nodeType, position=self.position)
|
||||||
# convert attributes from a list of tuples into a dict
|
# convert attributes from a list of tuples into a dict
|
||||||
attrValues = {key: value for (key, value) in self.inputs.items()}
|
attrValues = {key: value for (key, value) in self.inputs.items()}
|
||||||
|
intAttrValues = {key: value for (key, value) in self.internalInputs.items()}
|
||||||
|
|
||||||
# Use upgrade method of the node description itself if available
|
# Use upgrade method of the node description itself if available
|
||||||
try:
|
try:
|
||||||
|
@ -1421,9 +1534,15 @@ class CompatibilityNode(BaseNode):
|
||||||
logging.error("Error in the upgrade implementation of the node: {}. The return type is incorrect.".format(self.name))
|
logging.error("Error in the upgrade implementation of the node: {}. The return type is incorrect.".format(self.name))
|
||||||
upgradedAttrValues = attrValues
|
upgradedAttrValues = attrValues
|
||||||
|
|
||||||
upgradedAttrValuesTmp = {key: value for (key, value) in upgradedAttrValues.items() if key in commonInputs}
|
|
||||||
|
|
||||||
node.upgradeAttributeValues(upgradedAttrValues)
|
node.upgradeAttributeValues(upgradedAttrValues)
|
||||||
|
|
||||||
|
try:
|
||||||
|
upgradedIntAttrValues = node.nodeDesc.upgradeAttributeValues(intAttrValues, self.version)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("Error in the upgrade implementation of the node: {}.\n{}".format(self.name, str(e)))
|
||||||
|
upgradedIntAttrValues = intAttrValues
|
||||||
|
|
||||||
|
node.upgradeInternalAttributeValues(upgradedIntAttrValues)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
compatibilityIssue = Property(int, lambda self: self.issue.value, constant=True)
|
compatibilityIssue = Property(int, lambda self: self.issue.value, constant=True)
|
||||||
|
@ -1453,6 +1572,7 @@ def nodeFactory(nodeDict, name=None, template=False):
|
||||||
|
|
||||||
# get node inputs/outputs
|
# get node inputs/outputs
|
||||||
inputs = nodeDict.get("inputs", {})
|
inputs = nodeDict.get("inputs", {})
|
||||||
|
internalInputs = nodeDict.get("internalInputs", {})
|
||||||
outputs = nodeDict.get("outputs", {})
|
outputs = nodeDict.get("outputs", {})
|
||||||
version = nodeDict.get("version", None)
|
version = nodeDict.get("version", None)
|
||||||
internalFolder = nodeDict.get("internalFolder", None)
|
internalFolder = nodeDict.get("internalFolder", None)
|
||||||
|
@ -1475,16 +1595,38 @@ def nodeFactory(nodeDict, name=None, template=False):
|
||||||
compatibilityIssue = CompatibilityIssue.VersionConflict
|
compatibilityIssue = CompatibilityIssue.VersionConflict
|
||||||
# in other cases, check attributes compatibility between serialized node and its description
|
# in other cases, check attributes compatibility between serialized node and its description
|
||||||
else:
|
else:
|
||||||
# check that the node has the exact same set of inputs/outputs as its description, except if the node
|
# check that the node has the exact same set of inputs/outputs as its description, except
|
||||||
# is described in a template file, in which only non-default parameters are saved
|
# if the node is described in a template file, in which only non-default parameters are saved;
|
||||||
|
# do not perform that check for internal attributes because there is no point in
|
||||||
|
# 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]) != sorted(inputs.keys()) or \
|
if not template and (sorted([attr.name for attr in nodeDesc.inputs]) != sorted(inputs.keys()) or \
|
||||||
sorted([attr.name for attr in nodeDesc.outputs]) != sorted(outputs.keys())):
|
sorted([attr.name for attr in nodeDesc.outputs]) != sorted(outputs.keys())):
|
||||||
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
||||||
|
|
||||||
|
# check whether there are any internal attributes that are invalidating in the node description: if there
|
||||||
|
# are, then check that these internal attributes are part of nodeDict; if they are not, a compatibility
|
||||||
|
# issue must be raised to warn the user, as this will automatically change the node's UID
|
||||||
|
if not template:
|
||||||
|
invalidatingIntInputs = []
|
||||||
|
for attr in nodeDesc.internalInputs:
|
||||||
|
if attr.uid == [0]:
|
||||||
|
invalidatingIntInputs.append(attr.name)
|
||||||
|
for attr in invalidatingIntInputs:
|
||||||
|
if attr not in internalInputs.keys():
|
||||||
|
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
||||||
|
break
|
||||||
|
|
||||||
# verify that all inputs match their descriptions
|
# verify that all inputs match their descriptions
|
||||||
for attrName, value in inputs.items():
|
for attrName, value in inputs.items():
|
||||||
if not CompatibilityNode.attributeDescFromName(nodeDesc.inputs, attrName, value):
|
if not CompatibilityNode.attributeDescFromName(nodeDesc.inputs, attrName, value):
|
||||||
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
||||||
break
|
break
|
||||||
|
# verify that all internal inputs match their description
|
||||||
|
for attrName, value in internalInputs.items():
|
||||||
|
if not CompatibilityNode.attributeDescFromName(nodeDesc.internalInputs, attrName, value):
|
||||||
|
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
||||||
|
break
|
||||||
# verify that all outputs match their descriptions
|
# verify that all outputs match their descriptions
|
||||||
for attrName, value in outputs.items():
|
for attrName, value in outputs.items():
|
||||||
if not CompatibilityNode.attributeDescFromName(nodeDesc.outputs, attrName, value):
|
if not CompatibilityNode.attributeDescFromName(nodeDesc.outputs, attrName, value):
|
||||||
|
@ -1493,6 +1635,7 @@ def nodeFactory(nodeDict, name=None, template=False):
|
||||||
|
|
||||||
if compatibilityIssue is None:
|
if compatibilityIssue is None:
|
||||||
node = Node(nodeType, position, **inputs)
|
node = Node(nodeType, position, **inputs)
|
||||||
|
node.setInternalAttributeValues(internalInputs)
|
||||||
else:
|
else:
|
||||||
logging.warning("Compatibility issue detected for node '{}': {}".format(name, compatibilityIssue.name))
|
logging.warning("Compatibility issue detected for node '{}': {}".format(name, compatibilityIssue.name))
|
||||||
node = CompatibilityNode(nodeType, nodeDict, position, compatibilityIssue)
|
node = CompatibilityNode(nodeType, nodeDict, position, compatibilityIssue)
|
||||||
|
|
|
@ -264,11 +264,17 @@ class SetAttributeCommand(GraphCommand):
|
||||||
def redoImpl(self):
|
def redoImpl(self):
|
||||||
if self.value == self.oldValue:
|
if self.value == self.oldValue:
|
||||||
return False
|
return False
|
||||||
self.graph.attribute(self.attrName).value = self.value
|
if self.graph.attribute(self.attrName) is not None:
|
||||||
|
self.graph.attribute(self.attrName).value = self.value
|
||||||
|
else:
|
||||||
|
self.graph.internalAttribute(self.attrName).value = self.value
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def undoImpl(self):
|
def undoImpl(self):
|
||||||
self.graph.attribute(self.attrName).value = self.oldValue
|
if self.graph.attribute(self.attrName) is not None:
|
||||||
|
self.graph.attribute(self.attrName).value = self.oldValue
|
||||||
|
else:
|
||||||
|
self.graph.internalAttribute(self.attrName).value = self.oldValue
|
||||||
|
|
||||||
|
|
||||||
class AddEdgeCommand(GraphCommand):
|
class AddEdgeCommand(GraphCommand):
|
||||||
|
|
|
@ -710,7 +710,8 @@ class UIGraph(QObject):
|
||||||
""" Upgrade all upgradable CompatibilityNode instances in the graph. """
|
""" Upgrade all upgradable CompatibilityNode instances in the graph. """
|
||||||
with self.groupedGraphModification("Upgrade all Nodes"):
|
with self.groupedGraphModification("Upgrade all Nodes"):
|
||||||
nodes = [n for n in self._graph._compatibilityNodes.values() if n.canUpgrade]
|
nodes = [n for n in self._graph._compatibilityNodes.values() if n.canUpgrade]
|
||||||
for node in nodes:
|
sortedNodes = sorted(nodes, key=lambda x: x.name)
|
||||||
|
for node in sortedNodes:
|
||||||
self.upgradeNode(node)
|
self.upgradeNode(node)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Dialogs 1.0
|
||||||
import MaterialIcons 2.2
|
import MaterialIcons 2.2
|
||||||
import Utils 1.0
|
import Utils 1.0
|
||||||
|
|
||||||
|
@ -152,6 +153,12 @@ RowLayout {
|
||||||
case "BoolParam": return checkbox_component
|
case "BoolParam": return checkbox_component
|
||||||
case "ListAttribute": return listAttribute_component
|
case "ListAttribute": return listAttribute_component
|
||||||
case "GroupAttribute": return groupAttribute_component
|
case "GroupAttribute": return groupAttribute_component
|
||||||
|
case "StringParam":
|
||||||
|
if (attribute.desc.semantic === 'multiline')
|
||||||
|
return textArea_component
|
||||||
|
return textField_component
|
||||||
|
case "ColorParam":
|
||||||
|
return color_component
|
||||||
default: return textField_component
|
default: return textField_component
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +191,121 @@ RowLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: textArea_component
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
// Fixed background for the flickable object
|
||||||
|
color: palette.base
|
||||||
|
width: parent.width
|
||||||
|
height: 70
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: height
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: ScrollBar.AlwaysOn
|
||||||
|
}
|
||||||
|
|
||||||
|
TextArea.flickable: TextArea {
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
padding: 0
|
||||||
|
rightPadding: 5
|
||||||
|
bottomPadding: 2
|
||||||
|
topPadding: 2
|
||||||
|
readOnly: !root.editable
|
||||||
|
onEditingFinished: setTextFieldAttribute(text)
|
||||||
|
text: attribute.value
|
||||||
|
selectByMouse: true
|
||||||
|
onPressed: {
|
||||||
|
root.forceActiveFocus()
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (activeFocus)
|
||||||
|
setTextFieldAttribute(text)
|
||||||
|
}
|
||||||
|
DropArea {
|
||||||
|
enabled: root.editable
|
||||||
|
anchors.fill: parent
|
||||||
|
onDropped: {
|
||||||
|
if (drop.hasUrls)
|
||||||
|
setTextFieldAttribute(Filepath.urlToString(drop.urls[0]))
|
||||||
|
else if (drop.hasText && drop.text != '')
|
||||||
|
setTextFieldAttribute(drop.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: color_component
|
||||||
|
RowLayout {
|
||||||
|
CheckBox {
|
||||||
|
id: color_checkbox
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
checked: node && node.color === "" ? false : true
|
||||||
|
text: "Custom Color"
|
||||||
|
onClicked: {
|
||||||
|
if(checked) {
|
||||||
|
_reconstruction.setAttribute(attribute, "#0000FF")
|
||||||
|
} else {
|
||||||
|
_reconstruction.setAttribute(attribute, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: colorText
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
implicitWidth: 100
|
||||||
|
enabled: color_checkbox.checked
|
||||||
|
visible: enabled
|
||||||
|
text: enabled ? attribute.value : ""
|
||||||
|
selectByMouse: true
|
||||||
|
onEditingFinished: setTextFieldAttribute(text)
|
||||||
|
onAccepted: setTextFieldAttribute(text)
|
||||||
|
Component.onDestruction: {
|
||||||
|
if(activeFocus)
|
||||||
|
setTextFieldAttribute(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: colorText.height
|
||||||
|
width: colorText.width / 2
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
visible: color_checkbox.checked
|
||||||
|
color: color_checkbox.checked ? attribute.value : ""
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: colorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorDialog {
|
||||||
|
id: colorDialog
|
||||||
|
title: "Please choose a color"
|
||||||
|
color: attribute.value
|
||||||
|
onAccepted: {
|
||||||
|
colorText.text = color
|
||||||
|
// Artificially trigger change of attribute value
|
||||||
|
colorText.editingFinished()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
onRejected: close()
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
// Dummy item to fill out the space if needed
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: comboBox_component
|
id: comboBox_component
|
||||||
ComboBox {
|
ComboBox {
|
||||||
|
|
|
@ -73,6 +73,29 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatInternalAttributesTooltip(invalidation, comment) {
|
||||||
|
/*
|
||||||
|
* Creates a string that contains the invalidation message (if it is not empty) in bold,
|
||||||
|
* followed by the comment message (if it exists) in regular font, separated by an empty
|
||||||
|
* line.
|
||||||
|
* Invalidation and comment messages have their tabs or line returns in plain text format replaced
|
||||||
|
* by their HTML equivalents.
|
||||||
|
*/
|
||||||
|
let str = ""
|
||||||
|
if (invalidation !== "") {
|
||||||
|
let replacedInvalidation = node.invalidation.replace(/\n/g, "<br/>").replace(/\t/g, " ")
|
||||||
|
str += "<b>" + replacedInvalidation + "</b>"
|
||||||
|
}
|
||||||
|
if (invalidation !== "" && comment !== "") {
|
||||||
|
str += "<br/><br/>"
|
||||||
|
}
|
||||||
|
if (comment !== "") {
|
||||||
|
let replacedComment = node.comment.replace(/\n/g, "<br/>").replace(/\t/g, " ")
|
||||||
|
str += replacedComment
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
// Whether an attribute can be displayed as an attribute pin on the node
|
// Whether an attribute can be displayed as an attribute pin on the node
|
||||||
function isFileAttributeBaseType(attribute) {
|
function isFileAttributeBaseType(attribute) {
|
||||||
// ATM, only File attributes are meant to be connected
|
// ATM, only File attributes are meant to be connected
|
||||||
|
@ -138,7 +161,7 @@ Item {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: background
|
id: background
|
||||||
anchors.fill: nodeContent
|
anchors.fill: nodeContent
|
||||||
color: Qt.lighter(activePalette.base, 1.4)
|
color: node.color === "" ? Qt.lighter(activePalette.base, 1.4) : node.color
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
layer.effect: DropShadow { radius: 3; color: shadowColor }
|
layer.effect: DropShadow { radius: 3; color: shadowColor }
|
||||||
radius: 3
|
radius: 3
|
||||||
|
@ -181,6 +204,7 @@ Item {
|
||||||
|
|
||||||
// Node Name
|
// Node Name
|
||||||
Label {
|
Label {
|
||||||
|
id: nodeLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: node ? node.label : ""
|
text: node ? node.label : ""
|
||||||
padding: 4
|
padding: 4
|
||||||
|
@ -252,6 +276,35 @@ Item {
|
||||||
palette.text: "red"
|
palette.text: "red"
|
||||||
ToolTip.text: "Locked"
|
ToolTip.text: "Locked"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MaterialLabel {
|
||||||
|
id: nodeComment
|
||||||
|
visible: node.comment !== "" || node.invalidation !== ""
|
||||||
|
text: MaterialIcons.comment
|
||||||
|
padding: 2
|
||||||
|
font.pointSize: 7
|
||||||
|
|
||||||
|
ToolTip {
|
||||||
|
id: nodeCommentTooltip
|
||||||
|
parent: header
|
||||||
|
visible: nodeCommentMA.containsMouse && nodeComment.visible
|
||||||
|
text: formatInternalAttributesTooltip(node.invalidation, node.comment)
|
||||||
|
implicitWidth: 400 // Forces word-wrap for long comments but the tooltip will be bigger than needed for short comments
|
||||||
|
delay: 300
|
||||||
|
|
||||||
|
// Relative position for the tooltip to ensure we won't get stuck in a case where it starts appearing over the mouse's
|
||||||
|
// position because it's a bit long and cutting off the hovering of the mouse area (which leads to the tooltip beginning
|
||||||
|
// to appear and immediately disappearing, over and over again)
|
||||||
|
x: implicitWidth / 2.5
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
// If the node header is hovered, comments may be displayed
|
||||||
|
id: nodeCommentMA
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,7 @@ Panel {
|
||||||
currentIndex: tabBar.currentIndex
|
currentIndex: tabBar.currentIndex
|
||||||
|
|
||||||
AttributeEditor {
|
AttributeEditor {
|
||||||
|
id: inOutAttr
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
model: root.node.attributes
|
model: root.node.attributes
|
||||||
|
@ -247,6 +248,16 @@ Panel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
node: root.node
|
node: root.node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttributeEditor {
|
||||||
|
id: nodeInternalAttr
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: root.node.internalAttributes
|
||||||
|
readOnly: root.readOnly || root.isCompatibilityNode
|
||||||
|
onAttributeDoubleClicked: root.attributeDoubleClicked(mouse, attribute)
|
||||||
|
onUpgradeRequest: root.upgradeRequest()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,6 +296,12 @@ Panel {
|
||||||
leftPadding: 8
|
leftPadding: 8
|
||||||
rightPadding: leftPadding
|
rightPadding: leftPadding
|
||||||
}
|
}
|
||||||
|
TabButton {
|
||||||
|
text: "Notes"
|
||||||
|
padding: 4
|
||||||
|
leftPadding: 8
|
||||||
|
rightPadding: leftPadding
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ def test_templateVersions():
|
||||||
currentNodeVersion = meshroom.core.nodeVersion(nodeDesc)
|
currentNodeVersion = meshroom.core.nodeVersion(nodeDesc)
|
||||||
|
|
||||||
inputs = nodeData.get("inputs", {})
|
inputs = nodeData.get("inputs", {})
|
||||||
|
internalInputs = nodeData.get("internalInputs", {})
|
||||||
version = nodesVersions.get(nodeType, None)
|
version = nodesVersions.get(nodeType, None)
|
||||||
|
|
||||||
compatibilityIssue = None
|
compatibilityIssue = None
|
||||||
|
@ -49,5 +50,9 @@ def test_templateVersions():
|
||||||
if not CompatibilityNode.attributeDescFromName(nodeDesc.inputs, attrName, value):
|
if not CompatibilityNode.attributeDescFromName(nodeDesc.inputs, attrName, value):
|
||||||
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
||||||
break
|
break
|
||||||
|
for attrName, value in internalInputs.items():
|
||||||
|
if not CompatibilityNode.attributeDescFromName(nodeDesc.internalInputs, attrName, value):
|
||||||
|
compatibilityIssue = CompatibilityIssue.DescriptionConflict
|
||||||
|
break
|
||||||
|
|
||||||
assert compatibilityIssue is None
|
assert compatibilityIssue is None
|
||||||
|
|
Loading…
Add table
Reference in a new issue