[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
import meshroom.core
from meshroom.common import BaseObject, DictModel, Slot, Signal, Property, Variant, ListModel
from meshroom.core.exception import UnknownNodeTypeError
# Replace default encoder to support Enums
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]
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.
Args:
nodeType: the node type name
nodeDesc (desc.Node): the node description for this node
parent (BaseObject): this Node's parent
**kwargs: attributes values
"""
super(Node, self).__init__(parent)
self.nodeDesc = meshroom.core.nodesDesc[nodeType]()
self.nodeDesc = nodeDesc
self.packageName = self.nodeDesc.packageName
self.packageVersion = self.nodeDesc.packageVersion
@ -1049,6 +1050,47 @@ class Node(BaseObject):
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
GRAY = 1
BLACK = 2
@ -1162,7 +1204,12 @@ class Graph(BaseObject):
for nodeName, nodeData in sorted(graphData.items(), key=lambda x: self.getNodeIndexFromName(x[0])):
if not isinstance(nodeData, dict):
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
self._addNode(n, nodeName)
@ -1263,7 +1310,8 @@ class Graph(BaseObject):
"""
if name and name in self._nodes.keys():
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()
return n

View file

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