mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-28 09:47:20 +02:00
Merge pull request #2671 from alicevision/dev/nodeCreationCallback
NodeAPI: Trigger node creation callback only for explicit new node creation
This commit is contained in:
commit
6355037036
4 changed files with 97 additions and 36 deletions
|
@ -67,6 +67,11 @@ class Node(object):
|
|||
def upgradeAttributeValues(self, attrValues, fromVersion):
|
||||
return attrValues
|
||||
|
||||
@classmethod
|
||||
def onNodeCreated(cls, node):
|
||||
"""Called after a node instance had been created from this node descriptor and added to a Graph."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def update(cls, node):
|
||||
""" Method call before node's internal update on invalidation.
|
||||
|
|
|
@ -4,7 +4,7 @@ import json
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Iterable, Optional
|
||||
import weakref
|
||||
from collections import defaultdict, OrderedDict
|
||||
from contextlib import contextmanager
|
||||
|
@ -257,6 +257,11 @@ class Graph(BaseObject):
|
|||
"""
|
||||
self._deserialize(Graph._loadGraphData(filepath))
|
||||
|
||||
# Creating nodes from a template is conceptually similar to explicit node creation,
|
||||
# therefore the nodes descriptors' "onNodeCreated" callback is triggered for each
|
||||
# node instance created by this process.
|
||||
self._triggerNodeCreatedCallback(self.nodes)
|
||||
|
||||
if not publishOutputs:
|
||||
with GraphModification(self):
|
||||
for node in [node for node in self.nodes if node.nodeType == "Publish"]:
|
||||
|
@ -621,15 +626,17 @@ class Graph(BaseObject):
|
|||
|
||||
return inEdges, outEdges, outListAttributes
|
||||
|
||||
def addNewNode(self, nodeType, name=None, position=None, **kwargs):
|
||||
def addNewNode(
|
||||
self, nodeType: str, name: Optional[str] = None, position: Optional[str] = None, **kwargs
|
||||
) -> Node:
|
||||
"""
|
||||
Create and add a new node to the graph.
|
||||
|
||||
Args:
|
||||
nodeType (str): the node type name.
|
||||
name (str): if specified, the desired name for this node. If not unique, will be prefixed (_N).
|
||||
position (Position): (optional) the position of the node
|
||||
**kwargs: keyword arguments to initialize node's attributes
|
||||
nodeType: the node type name.
|
||||
name: if specified, the desired name for this node. If not unique, will be prefixed (_N).
|
||||
position: the position of the node.
|
||||
**kwargs: keyword arguments to initialize the created node's attributes.
|
||||
|
||||
Returns:
|
||||
The newly created node.
|
||||
|
@ -637,9 +644,17 @@ class Graph(BaseObject):
|
|||
if name and name in self._nodes.keys():
|
||||
name = self._createUniqueNodeName(name)
|
||||
|
||||
n = self.addNode(Node(nodeType, position=position, **kwargs), uniqueName=name)
|
||||
n.updateInternals()
|
||||
return n
|
||||
node = self.addNode(Node(nodeType, position=position, **kwargs), uniqueName=name)
|
||||
node.updateInternals()
|
||||
self._triggerNodeCreatedCallback([node])
|
||||
return node
|
||||
|
||||
def _triggerNodeCreatedCallback(self, nodes: Iterable[Node]):
|
||||
"""Trigger the `onNodeCreated` node descriptor callback for each node instance in `nodes`."""
|
||||
with GraphModification(self):
|
||||
for node in nodes:
|
||||
if node.nodeDesc:
|
||||
node.nodeDesc.onNodeCreated(node)
|
||||
|
||||
def _createUniqueNodeName(self, inputName: str, existingNames: Optional[set[str]] = None):
|
||||
"""Create a unique node name based on the input name.
|
||||
|
|
|
@ -1467,33 +1467,6 @@ class Node(BaseNode):
|
|||
if attr.invalidate:
|
||||
self.invalidatingAttributes.add(attr)
|
||||
|
||||
self.optionalCallOnDescriptor("onNodeCreated")
|
||||
|
||||
def optionalCallOnDescriptor(self, methodName, *args, **kwargs):
|
||||
""" Call of optional method defined in the descriptor.
|
||||
Available method names are:
|
||||
- onNodeCreated
|
||||
"""
|
||||
if hasattr(self.nodeDesc, methodName):
|
||||
m = getattr(self.nodeDesc, methodName)
|
||||
if callable(m):
|
||||
try:
|
||||
m(self, *args, **kwargs)
|
||||
except Exception:
|
||||
import traceback
|
||||
# Format error strings with all the provided arguments
|
||||
argsStr = ", ".join(str(arg) for arg in args)
|
||||
kwargsStr = ", ".join(str(key) + "=" + str(value) for key, value in kwargs.items())
|
||||
finalErrStr = argsStr
|
||||
if kwargsStr:
|
||||
if argsStr:
|
||||
finalErrStr += ", "
|
||||
finalErrStr += kwargsStr
|
||||
|
||||
logging.error("Error on call to '{}' (with args: '{}') for node type {}".
|
||||
format(methodName, finalErrStr, self.nodeType))
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
def setAttributeValues(self, values):
|
||||
# initialize attribute values
|
||||
for k, v in values.items():
|
||||
|
|
68
tests/test_nodeCallbacks.py
Normal file
68
tests/test_nodeCallbacks.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
from meshroom.core import desc, registerNodeType, unregisterNodeType
|
||||
from meshroom.core.node import Node
|
||||
from meshroom.core.graph import Graph, loadGraph
|
||||
|
||||
|
||||
class NodeWithCreationCallback(desc.InputNode):
|
||||
"""Node defining an 'onNodeCreated' callback, triggered a new node is added to a Graph."""
|
||||
|
||||
inputs = [
|
||||
desc.BoolParam(
|
||||
name="triggered",
|
||||
label="Triggered",
|
||||
description="Attribute impacted by the `onNodeCreated` callback",
|
||||
value=False,
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def onNodeCreated(cls, node: Node):
|
||||
"""Triggered when a new node is created within a Graph."""
|
||||
node.triggered.value = True
|
||||
|
||||
|
||||
class TestNodeCreationCallback:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
registerNodeType(NodeWithCreationCallback)
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
unregisterNodeType(NodeWithCreationCallback)
|
||||
|
||||
def test_notTriggeredOnNodeInstantiation(self):
|
||||
node = Node(NodeWithCreationCallback.__name__)
|
||||
assert node.triggered.value is False
|
||||
|
||||
def test_triggeredOnNewNodeCreationInGraph(self):
|
||||
graph = Graph("")
|
||||
node = graph.addNewNode(NodeWithCreationCallback.__name__)
|
||||
assert node.triggered.value is True
|
||||
|
||||
def test_notTriggeredOnNodeDuplication(self):
|
||||
graph = Graph("")
|
||||
node = graph.addNewNode(NodeWithCreationCallback.__name__)
|
||||
node.triggered.resetToDefaultValue()
|
||||
|
||||
duplicates = graph.duplicateNodes([node])
|
||||
assert duplicates[node][0].triggered.value is False
|
||||
|
||||
def test_notTriggeredOnGraphLoad(self, graphSavedOnDisk):
|
||||
graph: Graph = graphSavedOnDisk
|
||||
node = graph.addNewNode(NodeWithCreationCallback.__name__)
|
||||
node.triggered.resetToDefaultValue()
|
||||
graph.save()
|
||||
|
||||
loadedGraph = loadGraph(graph.filepath)
|
||||
assert loadedGraph.node(node.name).triggered.value is False
|
||||
|
||||
def test_triggeredOnGraphInitializationFromTemplate(self, graphSavedOnDisk):
|
||||
graph: Graph = graphSavedOnDisk
|
||||
node = graph.addNewNode(NodeWithCreationCallback.__name__)
|
||||
node.triggered.resetToDefaultValue()
|
||||
graph.save(template=True)
|
||||
|
||||
graphFromTemplate = Graph("")
|
||||
graphFromTemplate.initFromTemplate(graph.filepath)
|
||||
|
||||
assert graphFromTemplate.node(node.name).triggered.value is True
|
Loading…
Add table
Reference in a new issue