[core] Handle attribute valueChanged callback at the Node level

Move the attributeChanged callback logic within Node, as this
is a Node level mecanism (callbacks being declared on the node desc).

This commit also moves the attribute valueChanged signal outside of
the Attribute's constructor.
This seems to be the root cause to undesired side-effects breaking Qt's evaluation
system, data model access and consequently Meshroom's UI.
This commit is contained in:
Yann Lanthony 2024-10-25 19:12:21 +02:00
parent e73e8f2fd7
commit 7fdb5cc734
2 changed files with 35 additions and 15 deletions

View file

@ -25,11 +25,14 @@ def attributeFactory(description, value, isOutput, node, root=None, parent=None)
root: (optional) parent Attribute (must be ListAttribute or GroupAttribute)
parent (BaseObject): (optional) the parent BaseObject if any
"""
attr = description.instanceType(node, description, isOutput, root, parent)
attr: Attribute = description.instanceType(node, description, isOutput, root, parent)
if value is not None:
attr._set_value(value, emitSignals=False)
else:
attr.resetToDefaultValue(emitSignals=False)
attr.valueChanged.connect(lambda attr=attr: node._onAttributeChanged(attr))
return attr
@ -67,7 +70,6 @@ class Attribute(BaseObject):
self._value = None
self.initValue()
self.valueChanged.connect(self.onChanged)
@property
def node(self):

View file

@ -14,6 +14,7 @@ import types
import uuid
from collections import namedtuple
from enum import Enum
from typing import Callable, Optional
import meshroom
from meshroom.common import Signal, Variant, Property, BaseObject, Slot, ListModel, DictModel
@ -929,25 +930,42 @@ class BaseNode(BaseObject):
def _updateChunks(self):
pass
def onAttributeChanged(self, attr):
""" When an attribute changed, a specific function can be defined in the descriptor and be called.
def _getAttributeChangedCallback(self, attr: Attribute) -> Optional[Callable]:
"""Get the node descriptor-defined value changed callback associated to `attr` if any."""
attrCapitalizedName = attr.name[:1].upper() + attr.name[1:]
callbackName = f"on{attrCapitalizedName}Changed"
callback = getattr(self.nodeDesc, callbackName, None)
return callback if callback and callable(callback) else None
def _onAttributeChanged(self, attr: Attribute):
"""
When an attribute value has changed, a specific function can be defined in the descriptor and be called.
Args:
attr (Attribute): attribute that has changed
attr: The Attribute that has changed.
"""
# Call the specific function if it exists in the node implementation
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)
if self.isCompatibilityNode:
# Compatibility nodes are not meant to be updated.
return
if attr.isOutput and not self.isInputNode:
# Ignore changes on output attributes for non-input nodes
# as they are updated during the node's computation.
# And we do not want notifications during the graph processing.
return
callback = self._getAttributeChangedCallback(attr)
if callback:
callback(self)
if self.graph:
# If we are in a graph, propagate the notification to the connected output attributes
outEdges = self.graph.outEdges(attr)
for edge in outEdges:
edge.dst.onChanged()
for edge in self.graph.outEdges(attr):
edge.dst.node._onAttributeChanged(edge.dst)
def onAttributeClicked(self, attr):
""" When an attribute is clicked, a specific function can be defined in the descriptor and be called.