mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-28 17:57:16 +02:00
[core] ChoiceParam: add option to serialize overriden values
Introduce a new `saveValuesOverride` parameter on desc.ChoiceParam to define whether to serialize the list of possible values if they have been overridden at runtime.
This commit is contained in:
parent
8ee7b50204
commit
062bc3ca28
3 changed files with 220 additions and 6 deletions
|
@ -472,11 +472,16 @@ class PushButtonParam(Attribute):
|
||||||
|
|
||||||
class ChoiceParam(Attribute):
|
class ChoiceParam(Attribute):
|
||||||
|
|
||||||
def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
|
def __init__(self, node, attributeDesc: desc.ChoiceParam, isOutput, root=None, parent=None):
|
||||||
super(ChoiceParam, self).__init__(node, attributeDesc, isOutput, root, parent)
|
super(ChoiceParam, self).__init__(node, attributeDesc, isOutput, root, parent)
|
||||||
self._values = None
|
self._values = None
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.getValues())
|
||||||
|
|
||||||
def getValues(self):
|
def getValues(self):
|
||||||
|
if (linkParam := self.getLinkParam()) is not None:
|
||||||
|
return linkParam.getValues()
|
||||||
return self._values if self._values is not None else self.desc._values
|
return self._values if self._values is not None else self.desc._values
|
||||||
|
|
||||||
def conformValue(self, val):
|
def conformValue(self, val):
|
||||||
|
@ -494,6 +499,15 @@ class ChoiceParam(Attribute):
|
||||||
raise ValueError("Non exclusive ChoiceParam value should be iterable (param:{}, value:{}, type:{})".
|
raise ValueError("Non exclusive ChoiceParam value should be iterable (param:{}, value:{}, type:{})".
|
||||||
format(self.name, value, type(value)))
|
format(self.name, value, type(value)))
|
||||||
return [self.conformValue(v) for v in value]
|
return [self.conformValue(v) for v in value]
|
||||||
|
|
||||||
|
def _set_value(self, value):
|
||||||
|
# Handle alternative serialization for ChoiceParam with overriden values.
|
||||||
|
serializedValueWithValuesOverrides = isinstance(value, dict)
|
||||||
|
if serializedValueWithValuesOverrides:
|
||||||
|
super()._set_value(value[self.desc._OVERRIDE_SERIALIZATION_KEY_VALUE])
|
||||||
|
self.setValues(value[self.desc._OVERRIDE_SERIALIZATION_KEY_VALUES])
|
||||||
|
else:
|
||||||
|
super()._set_value(value)
|
||||||
|
|
||||||
def setValues(self, values):
|
def setValues(self, values):
|
||||||
if values == self._values:
|
if values == self._values:
|
||||||
|
@ -501,9 +515,18 @@ class ChoiceParam(Attribute):
|
||||||
self._values = values
|
self._values = values
|
||||||
self.valuesChanged.emit()
|
self.valuesChanged.emit()
|
||||||
|
|
||||||
def __len__(self):
|
def getExportValue(self):
|
||||||
return len(self.getValues())
|
useStandardSerialization = self.isLink or not self.desc._saveValuesOverride or self._values is None
|
||||||
|
|
||||||
|
if useStandardSerialization:
|
||||||
|
return super().getExportValue()
|
||||||
|
|
||||||
|
return {
|
||||||
|
self.desc._OVERRIDE_SERIALIZATION_KEY_VALUE: self._value,
|
||||||
|
self.desc._OVERRIDE_SERIALIZATION_KEY_VALUES: self._values,
|
||||||
|
}
|
||||||
|
|
||||||
|
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
||||||
valuesChanged = Signal()
|
valuesChanged = Signal()
|
||||||
values = Property(Variant, getValues, setValues, notify=valuesChanged)
|
values = Property(Variant, getValues, setValues, notify=valuesChanged)
|
||||||
|
|
||||||
|
|
|
@ -411,16 +411,33 @@ class PushButtonParam(Param):
|
||||||
|
|
||||||
class ChoiceParam(Param):
|
class ChoiceParam(Param):
|
||||||
"""
|
"""
|
||||||
|
ChoiceParam is an Attribute that allows to choose a value among a list of possible values.
|
||||||
|
|
||||||
|
When using `exclusive=True`, the value is a single element of the list of possible values.
|
||||||
|
When using `exclusive=False`, the value is a list of elements of the list of possible values.
|
||||||
|
|
||||||
|
Despite this being the standard behavior, ChoiceParam also supports custom value: it is possible to set any value,
|
||||||
|
even outside list of possible values.
|
||||||
|
|
||||||
|
The list of possible values on a ChoiceParam instance can be overriden at runtime.
|
||||||
|
If those changes needs to be persisted, `saveValuesOverride` should be set to True.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, label, description, value, values, exclusive=True, group="allParams", joinChar=" ",
|
|
||||||
advanced=False, enabled=True, invalidate=True, semantic="", validValue=True, errorMessage="",
|
# Keys for values override serialization schema (saveValuesOverride=True).
|
||||||
|
_OVERRIDE_SERIALIZATION_KEY_VALUE = "__ChoiceParam_value__"
|
||||||
|
_OVERRIDE_SERIALIZATION_KEY_VALUES = "__ChoiceParam_values__"
|
||||||
|
|
||||||
|
def __init__(self, name: str, label: str, description: str, value, values, exclusive=True, saveValuesOverride=False,
|
||||||
|
group="allParams", joinChar=" ", advanced=False, enabled=True, invalidate=True, semantic="",
|
||||||
|
validValue=True, errorMessage="",
|
||||||
visible=True, exposed=False):
|
visible=True, exposed=False):
|
||||||
assert values
|
|
||||||
super(ChoiceParam, self).__init__(name=name, label=label, description=description, value=value,
|
super(ChoiceParam, self).__init__(name=name, label=label, description=description, value=value,
|
||||||
group=group, advanced=advanced, enabled=enabled, invalidate=invalidate,
|
group=group, advanced=advanced, enabled=enabled, invalidate=invalidate,
|
||||||
semantic=semantic, validValue=validValue, errorMessage=errorMessage,
|
semantic=semantic, validValue=validValue, errorMessage=errorMessage,
|
||||||
visible=visible, exposed=exposed)
|
visible=visible, exposed=exposed)
|
||||||
self._values = values
|
self._values = values
|
||||||
|
self._saveValuesOverride = saveValuesOverride
|
||||||
self._exclusive = exclusive
|
self._exclusive = exclusive
|
||||||
self._joinChar = joinChar
|
self._joinChar = joinChar
|
||||||
if self._values:
|
if self._values:
|
||||||
|
@ -447,6 +464,11 @@ class ChoiceParam(Param):
|
||||||
def validateValue(self, value):
|
def validateValue(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
serializedWithValuesOverride = isinstance(value, dict)
|
||||||
|
if serializedWithValuesOverride:
|
||||||
|
value = value[ChoiceParam._OVERRIDE_SERIALIZATION_KEY_VALUE]
|
||||||
|
|
||||||
if self.exclusive:
|
if self.exclusive:
|
||||||
return self.conformValue(value)
|
return self.conformValue(value)
|
||||||
|
|
||||||
|
|
169
tests/test_attributeChoiceParam.py
Normal file
169
tests/test_attributeChoiceParam.py
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
from meshroom.core import desc, registerNodeType, unregisterNodeType
|
||||||
|
from meshroom.core.graph import Graph, loadGraph
|
||||||
|
|
||||||
|
|
||||||
|
class NodeWithChoiceParams(desc.Node):
|
||||||
|
inputs = [
|
||||||
|
desc.ChoiceParam(
|
||||||
|
name="choice",
|
||||||
|
label="Choice Default Serialization",
|
||||||
|
description="A choice parameter with standard serialization",
|
||||||
|
value="A",
|
||||||
|
values=["A", "B", "C"],
|
||||||
|
saveValuesOverride=False,
|
||||||
|
exclusive=True,
|
||||||
|
exposed=True,
|
||||||
|
),
|
||||||
|
desc.ChoiceParam(
|
||||||
|
name="choiceMulti",
|
||||||
|
label="Choice Default Serialization",
|
||||||
|
description="A choice parameter with standard serialization",
|
||||||
|
value=["A"],
|
||||||
|
values=["A", "B", "C"],
|
||||||
|
saveValuesOverride=False,
|
||||||
|
exclusive=False,
|
||||||
|
exposed=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class NodeWithChoiceParamsSavingValuesOverride(desc.Node):
|
||||||
|
inputs = [
|
||||||
|
desc.ChoiceParam(
|
||||||
|
name="choice",
|
||||||
|
label="Choice Custom Serialization",
|
||||||
|
description="A choice parameter with serialization of overriden values",
|
||||||
|
value="A",
|
||||||
|
values=["A", "B", "C"],
|
||||||
|
saveValuesOverride=True,
|
||||||
|
exclusive=True,
|
||||||
|
exposed=True,
|
||||||
|
),
|
||||||
|
desc.ChoiceParam(
|
||||||
|
name="choiceMulti",
|
||||||
|
label="Choice Custom Serialization",
|
||||||
|
description="A choice parameter with serialization of overriden values",
|
||||||
|
value=["A"],
|
||||||
|
values=["A", "B", "C"],
|
||||||
|
saveValuesOverride=True,
|
||||||
|
exclusive=False,
|
||||||
|
exposed=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TestChoiceParam:
|
||||||
|
@classmethod
|
||||||
|
def setup_class(cls):
|
||||||
|
registerNodeType(NodeWithChoiceParams)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def teardown_class(cls):
|
||||||
|
unregisterNodeType(NodeWithChoiceParams)
|
||||||
|
|
||||||
|
def test_customValueIsSerialized(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
|
||||||
|
node = graph.addNewNode(NodeWithChoiceParams.__name__)
|
||||||
|
node.choice.value = "CustomValue"
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
loadedGraph = loadGraph(graph.filepath)
|
||||||
|
assert loadedGraph.node(node.name).choice.value == "CustomValue"
|
||||||
|
|
||||||
|
def test_customMultiValueIsSerialized(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
|
||||||
|
node = graph.addNewNode(NodeWithChoiceParams.__name__)
|
||||||
|
node.choiceMulti.value = ["custom", "value"]
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
loadedGraph = loadGraph(graph.filepath)
|
||||||
|
assert loadedGraph.node(node.name).choiceMulti.value == ["custom", "value"]
|
||||||
|
|
||||||
|
def test_overridenValuesAreNotSerialized(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
node = graph.addNewNode(NodeWithChoiceParams.__name__)
|
||||||
|
node.choice.values = ["D", "E", "F"]
|
||||||
|
|
||||||
|
graph.save()
|
||||||
|
loadedGraph = loadGraph(graph.filepath)
|
||||||
|
|
||||||
|
assert loadedGraph.node(node.name).choice.values == ["A", "B", "C"]
|
||||||
|
|
||||||
|
def test_connectionPropagatesOverridenValues(self):
|
||||||
|
graph = Graph("")
|
||||||
|
|
||||||
|
nodeA = graph.addNewNode(NodeWithChoiceParams.__name__)
|
||||||
|
nodeB = graph.addNewNode(NodeWithChoiceParams.__name__)
|
||||||
|
nodeA.choice.values = ["D", "E", "F"]
|
||||||
|
graph.addEdge(nodeA.choice, nodeB.choice)
|
||||||
|
|
||||||
|
assert nodeB.choice.values == ["D", "E", "F"]
|
||||||
|
|
||||||
|
def test_connectionsAreSerialized(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
nodeA = graph.addNewNode(NodeWithChoiceParams.__name__)
|
||||||
|
nodeB = graph.addNewNode(NodeWithChoiceParams.__name__)
|
||||||
|
graph.addEdge(nodeA.choice, nodeB.choice)
|
||||||
|
graph.addEdge(nodeA.choiceMulti, nodeB.choiceMulti)
|
||||||
|
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
loadedGraph = loadGraph(graph.filepath)
|
||||||
|
loadedNodeA = loadedGraph.node(nodeA.name)
|
||||||
|
loadedNodeB = loadedGraph.node(nodeB.name)
|
||||||
|
assert loadedNodeB.choice.linkParam == loadedNodeA.choice
|
||||||
|
assert loadedNodeB.choiceMulti.linkParam == loadedNodeA.choiceMulti
|
||||||
|
|
||||||
|
|
||||||
|
class TestChoiceParamSavingCustomValues:
|
||||||
|
@classmethod
|
||||||
|
def setup_class(cls):
|
||||||
|
registerNodeType(NodeWithChoiceParamsSavingValuesOverride)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def teardown_class(cls):
|
||||||
|
unregisterNodeType(NodeWithChoiceParamsSavingValuesOverride)
|
||||||
|
|
||||||
|
def test_customValueIsSerialized(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
|
||||||
|
node = graph.addNewNode(NodeWithChoiceParamsSavingValuesOverride.__name__)
|
||||||
|
node.choice.value = "CustomValue"
|
||||||
|
node.choiceMulti.value = ["custom", "value"]
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
loadedGraph = loadGraph(graph.filepath)
|
||||||
|
assert loadedGraph.node(node.name).choice.value == "CustomValue"
|
||||||
|
assert loadedGraph.node(node.name).choiceMulti.value == ["custom", "value"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_overridenValuesAreSerialized(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
node = graph.addNewNode(NodeWithChoiceParamsSavingValuesOverride.__name__)
|
||||||
|
node.choice.values = ["D", "E", "F"]
|
||||||
|
node.choiceMulti.values = ["D", "E", "F"]
|
||||||
|
|
||||||
|
graph.save()
|
||||||
|
loadedGraph = loadGraph(graph.filepath)
|
||||||
|
|
||||||
|
loadedNode = loadedGraph.node(node.name)
|
||||||
|
|
||||||
|
assert loadedNode.choice.values == ["D", "E", "F"]
|
||||||
|
assert loadedNode.choiceMulti.values == ["D", "E", "F"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_connectionsAreSerialized(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
nodeA = graph.addNewNode(NodeWithChoiceParamsSavingValuesOverride.__name__)
|
||||||
|
nodeB = graph.addNewNode(NodeWithChoiceParamsSavingValuesOverride.__name__)
|
||||||
|
graph.addEdge(nodeA.choice, nodeB.choice)
|
||||||
|
graph.addEdge(nodeA.choiceMulti, nodeB.choiceMulti)
|
||||||
|
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
loadedGraph = loadGraph(graph.filepath)
|
||||||
|
loadedNodeA = loadedGraph.node(nodeA.name)
|
||||||
|
loadedNodeB = loadedGraph.node(nodeB.name)
|
||||||
|
assert loadedNodeB.choice.linkParam == loadedNodeA.choice
|
||||||
|
assert loadedNodeB.choiceMulti.linkParam == loadedNodeA.choiceMulti
|
Loading…
Add table
Reference in a new issue