[core] add low-level retro-compatibility for attribute changes

First version of retrocompatibility, allowing to load files referencing removed or type-incompatible attributes.
* add node_factory to centralize node instantiation
* discard invalid attributes (i.e. not part of the node description anymore or with incompatible value type) when loading a file
* raise on unknown nodes
* add 'core.exception' module to declare Meshroom's exception types
This commit is contained in:
Yann Lanthony 2018-04-13 21:55:27 +02:00
parent f401ca7c8b
commit 0adc4d8cc6
3 changed files with 77 additions and 7 deletions

View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
# coding:utf-8
class MeshroomException(Exception):
""" Base class for Meshroom exceptions """
pass
class GraphException(MeshroomException):
""" Base class for Graph exceptions """
pass
class UnknownNodeTypeError(GraphException):
"""
Raised when asked to create a unknown node type.
"""
def __init__(self, nodeType):
msg = "Unknown Node Type: " + nodeType
super(UnknownNodeTypeError, self).__init__(msg)
self.nodeType = nodeType

View file

@ -20,6 +20,7 @@ from . import stats
from . import desc from . import desc
import meshroom.core import meshroom.core
from meshroom.common import BaseObject, DictModel, Slot, Signal, Property, Variant, ListModel from meshroom.common import BaseObject, DictModel, Slot, Signal, Property, Variant, ListModel
from meshroom.core.exception import UnknownNodeTypeError
# Replace default encoder to support Enums # Replace default encoder to support Enums
DefaultJSONEncoder = json.JSONEncoder # store the original one DefaultJSONEncoder = json.JSONEncoder # store the original one
@ -725,18 +726,18 @@ class Node(BaseObject):
# i.e: a.b, a[0], a[0].b.c[1] # i.e: a.b, a[0], a[0].b.c[1]
attributeRE = re.compile(r'\.?(?P<name>\w+)(?:\[(?P<index>\d+)\])?') attributeRE = re.compile(r'\.?(?P<name>\w+)(?:\[(?P<index>\d+)\])?')
def __init__(self, nodeType, parent=None, **kwargs): def __init__(self, nodeDesc, parent=None, **kwargs):
""" """
Create a new Node instance based of the given node type name (name of a desc.Node subclass). Create a new Node instance based on the given node description.
Any other keyword argument will be used to initialize this node's attributes. Any other keyword argument will be used to initialize this node's attributes.
Args: Args:
nodeType: the node type name nodeDesc (desc.Node): the node description for this node
parent (BaseObject): this Node's parent parent (BaseObject): this Node's parent
**kwargs: attributes values **kwargs: attributes values
""" """
super(Node, self).__init__(parent) super(Node, self).__init__(parent)
self.nodeDesc = meshroom.core.nodesDesc[nodeType]() self.nodeDesc = nodeDesc
self.packageName = self.nodeDesc.packageName self.packageName = self.nodeDesc.packageName
self.packageVersion = self.nodeDesc.packageVersion self.packageVersion = self.nodeDesc.packageVersion
@ -1049,6 +1050,47 @@ class Node(BaseObject):
size = Property(int, getSize, notify=sizeChanged) size = Property(int, getSize, notify=sizeChanged)
def node_factory(nodeType, skipInvalidAttributes=False, **attributes):
"""
Create a new Node of type NodeType and initialize its attributes with given kwargs.
Args:
nodeType (str): name of the node description class
skipInvalidAttributes (bool): whether to skip attributes not defined in
or incompatible with nodeType's description.
attributes (): serialized nodes attributes
Raises:
UnknownNodeTypeError if nodeType is unknown
"""
try:
nodeDesc = meshroom.core.nodesDesc[nodeType]()
except KeyError:
# unknown node type
raise UnknownNodeTypeError(nodeType)
if skipInvalidAttributes:
# compare given attributes with the ones from node desc
descAttrNames = set([attr.name for attr in nodeDesc.inputs])
attrNames = set([name for name in attributes.keys()])
invalidAttributes = list(attrNames.difference(descAttrNames))
commonAttributes = list(attrNames.intersection(descAttrNames))
# compare value types for common attributes
for attr in [attr for attr in nodeDesc.inputs if attr.name in commonAttributes]:
try:
attr.validateValue(attributes[attr.name])
except:
invalidAttributes.append(attr.name)
if invalidAttributes and skipInvalidAttributes:
# filter out invalid attributes
logging.info("Skipping invalid attributes initialization for {}: {}".format(nodeType, invalidAttributes))
for attr in invalidAttributes:
del attributes[attr]
return Node(nodeDesc, **attributes)
WHITE = 0 WHITE = 0
GRAY = 1 GRAY = 1
BLACK = 2 BLACK = 2
@ -1162,7 +1204,12 @@ class Graph(BaseObject):
for nodeName, nodeData in sorted(graphData.items(), key=lambda x: self.getNodeIndexFromName(x[0])): for nodeName, nodeData in sorted(graphData.items(), key=lambda x: self.getNodeIndexFromName(x[0])):
if not isinstance(nodeData, dict): if not isinstance(nodeData, dict):
raise RuntimeError('loadGraph error: Node is not a dict. File: {}'.format(filepath)) raise RuntimeError('loadGraph error: Node is not a dict. File: {}'.format(filepath))
n = Node(nodeData['nodeType'], **nodeData['attributes'])
n = node_factory(nodeData['nodeType'],
# allow simple retro-compatibility, though cache might get invalidated
skipInvalidAttributes=True,
**nodeData['attributes'])
# Add node to the graph with raw attributes values # Add node to the graph with raw attributes values
self._addNode(n, nodeName) self._addNode(n, nodeName)
@ -1263,7 +1310,8 @@ class Graph(BaseObject):
""" """
if name and name in self._nodes.keys(): if name and name in self._nodes.keys():
name = self._createUniqueNodeName(name) name = self._createUniqueNodeName(name)
n = self.addNode(Node(nodeType=nodeType, **kwargs), uniqueName=name)
n = self.addNode(node_factory(nodeType, False, **kwargs), uniqueName=name)
n.updateInternals() n.updateInternals()
return n return n

View file

@ -330,7 +330,7 @@ class Reconstruction(UIGraph):
# * create an uninitialized node # * create an uninitialized node
# * wait for the result before actually creating new nodes in the graph (see onIntrinsicsAvailable) # * wait for the result before actually creating new nodes in the graph (see onIntrinsicsAvailable)
attributes = cameraInit.toDict()["attributes"] if cameraInit else {} attributes = cameraInit.toDict()["attributes"] if cameraInit else {}
cameraInitCopy = graph.Node("CameraInit", **attributes) cameraInitCopy = graph.node_factory("CameraInit", **attributes)
try: try:
self.setBuildingIntrinsics(True) self.setBuildingIntrinsics(True)