[core] Add new type of ChoiceParam that changes dynamically according to other values

Add new type of ChoiceParam that changes dynamically according to other
values.

When value of an attribute is changed onAttributeChanged is called,
allowing to have unique reaction within node files.

Also add of callDesc function to be able to have other functions such as
onNodeCreated at creation of node.
This commit is contained in:
Aurore LAFAURIE 2024-03-27 16:51:24 +01:00
parent bb9661a141
commit 498fd6cbd2
4 changed files with 68 additions and 11 deletions

View file

@ -7,7 +7,7 @@ import weakref
import types import types
import logging import logging
from collections.abc import Sequence from collections.abc import Iterable, Sequence
from string import Template from string import Template
from meshroom.common import BaseObject, Property, Variant, Signal, ListModel, DictModel, Slot from meshroom.common import BaseObject, Property, Variant, Signal, ListModel, DictModel, Slot
from meshroom.core import desc, hashValue from meshroom.core import desc, hashValue
@ -29,6 +29,8 @@ def attributeFactory(description, value, isOutput, node, root=None, parent=None)
cls = GroupAttribute cls = GroupAttribute
elif isinstance(description, desc.ListAttribute): elif isinstance(description, desc.ListAttribute):
cls = ListAttribute cls = ListAttribute
elif isinstance(description, desc.ChoiceParam):
cls = ChoiceParam
else: else:
cls = Attribute cls = Attribute
attr = cls(node, description, isOutput, root, parent) attr = cls(node, description, isOutput, root, parent)
@ -170,6 +172,9 @@ class Attribute(BaseObject):
return return
self._validValue = value self._validValue = value
def validateValue(self, value):
return self.desc.validateValue(value)
def _get_value(self): def _get_value(self):
if self.isLink: if self.isLink:
return self.getLinkParam().value return self.getLinkParam().value
@ -185,8 +190,10 @@ class Attribute(BaseObject):
else: else:
# if we set a new value, we use the attribute descriptor validator to check the validity of the value # if we set a new value, we use the attribute descriptor validator to check the validity of the value
# and apply some conversion if needed # and apply some conversion if needed
convertedValue = self.desc.validateValue(value) convertedValue = self.validateValue(value)
self._value = convertedValue self._value = convertedValue
self.node.onAttributeChanged(self)
# Request graph update when input parameter value is set # Request graph update when input parameter value is set
# and parent node belongs to a graph # and parent node belongs to a graph
# Output attributes value are set internally during the update process, # Output attributes value are set internally during the update process,
@ -392,6 +399,43 @@ def raiseIfLink(func):
return wrapper return wrapper
class ChoiceParam(Attribute):
def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
super(ChoiceParam, self).__init__(node, attributeDesc, isOutput, root, parent)
self._values = None
def getValues(self):
return self._values if self._values is not None else self.desc._values
def conformValue(self, val):
""" Conform 'val' to the correct type and check for its validity """
return self.desc._valueType(val)
def validateValue(self, value):
if self.desc.exclusive:
return self.conformValue(value)
if isinstance(value, str):
value = value.split(',')
if not isinstance(value, Iterable):
raise ValueError('Non exclusive ChoiceParam value should be iterable (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))
return [self.conformValue(v) for v in value]
def setValues(self, values):
if values == self._values:
return
self._values = values
self.valuesChanged.emit()
def __len__(self):
return len(self.getValues())
valuesChanged = Signal()
values = Property(Variant, getValues, setValues, notify=valuesChanged)
class ListAttribute(Attribute): class ListAttribute(Attribute):
def __init__(self, node, attributeDesc, isOutput, root=None, parent=None): def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
@ -567,7 +611,7 @@ class GroupAttribute(Attribute):
raise AttributeError(key) raise AttributeError(key)
def _set_value(self, exportedValue): def _set_value(self, exportedValue):
value = self.desc.validateValue(exportedValue) value = self.validateValue(exportedValue)
if isinstance(value, dict): if isinstance(value, dict):
# set individual child attribute values # set individual child attribute values
for key, v in value.items(): for key, v in value.items():
@ -581,7 +625,7 @@ class GroupAttribute(Attribute):
raise AttributeError("Failed to set on GroupAttribute: {}".format(str(value))) raise AttributeError("Failed to set on GroupAttribute: {}".format(str(value)))
def upgradeValue(self, exportedValue): def upgradeValue(self, exportedValue):
value = self.desc.validateValue(exportedValue) value = self.validateValue(exportedValue)
if isinstance(value, dict): if isinstance(value, dict):
# set individual child attribute values # set individual child attribute values
for key, v in value.items(): for key, v in value.items():

View file

@ -313,12 +313,11 @@ class ChoiceParam(Param):
super(ChoiceParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, super(ChoiceParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced,
semantic=semantic, enabled=enabled, validValue=validValue, errorMessage=errorMessage) semantic=semantic, enabled=enabled, validValue=validValue, errorMessage=errorMessage)
def conformValue(self, val): def conformValue(self, value):
""" Conform 'val' to the correct type and check for its validity """ """ Conform 'val' to the correct type and check for its validity """
val = self._valueType(val) if not isinstance(value, str):
if val not in self.values: raise ValueError('ChoiceParam value should be a string (param:{}, value:{}, type:{})'.format(self.name, value, type(value)))
raise ValueError('ChoiceParam value "{}" is not in "{}".'.format(val, str(self.values))) return value
return val
def validateValue(self, value): def validateValue(self, value):
if self.exclusive: if self.exclusive:
@ -573,7 +572,6 @@ class Node(object):
BaseNode.updateInternals BaseNode.updateInternals
""" """
pass pass
@classmethod @classmethod
def postUpdate(cls, node): def postUpdate(cls, node):
""" Method call after node's internal update on invalidation. """ Method call after node's internal update on invalidation.

View file

@ -879,6 +879,14 @@ class BaseNode(BaseObject):
def _updateChunks(self): def _updateChunks(self):
pass pass
def onAttributeChanged(self, attr):
paramName = attr.name[:1].upper() + attr.name[1:]
methodName = f'on{paramName}Changed'
if hasattr(self.nodeDesc, methodName):
m = getattr(self.nodeDesc, methodName)
if callable(m):
m(self)
def updateInternals(self, cacheDir=None): def updateInternals(self, cacheDir=None):
""" Update Node's internal parameters and output attributes. """ Update Node's internal parameters and output attributes.
@ -1245,6 +1253,13 @@ class Node(BaseNode):
self.attributesPerUid[uidIndex].add(attr) self.attributesPerUid[uidIndex].add(attr)
self.setAttributeValues(kwargs) self.setAttributeValues(kwargs)
self.callDesc("onNodeCreated")
def callDesc(self, methodName, *args, **kwargs):
if hasattr(self.nodeDesc, methodName):
m = getattr(self.nodeDesc, methodName)
if callable(m):
m(self, *args, **kwargs)
def setAttributeValues(self, values): def setAttributeValues(self, values):
# initialize attribute values # initialize attribute values

View file

@ -345,7 +345,7 @@ RowLayout {
ComboBox { ComboBox {
id: combo id: combo
enabled: root.editable enabled: root.editable
model: attribute.desc.values model: attribute.values
Component.onCompleted: currentIndex = find(attribute.value) Component.onCompleted: currentIndex = find(attribute.value)
onActivated: _reconstruction.setAttribute(attribute, currentText) onActivated: _reconstruction.setAttribute(attribute, currentText)
Connections { Connections {