mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-06 10:18:42 +02:00
[core] Plugins: Introduction of NodePluginManager
The NodePluginManager provides a way for registering and managing node plugins within Meshroom. This also provides a way for other components to interact with the plugins to understand whether a plugin is available or not.
This commit is contained in:
parent
648b0950b8
commit
02b85cb273
11 changed files with 600 additions and 149 deletions
8
meshroom/_plugins/__init__.py
Normal file
8
meshroom/_plugins/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
""" Plugins.
|
||||||
|
"""
|
||||||
|
# Plugins
|
||||||
|
from .node import NodePluginManager
|
||||||
|
from .base import Status, Pluginator
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["NodePluginManager", "Status", "Pluginator"]
|
92
meshroom/_plugins/base.py
Normal file
92
meshroom/_plugins/base.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
""" Base functionality for Plugins.
|
||||||
|
"""
|
||||||
|
# Types
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# STD
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import enum
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
|
||||||
|
class Status(enum.IntEnum):
|
||||||
|
""" Enum describing the state of the plugin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# UNLOADED or NOT Available - Describing that the plugin is not available in the current set of plugins
|
||||||
|
UNLOADED = -1
|
||||||
|
|
||||||
|
# ERRORED describes that the plugin exists but could not be loaded due to errors with the structure
|
||||||
|
ERRORED = 0
|
||||||
|
|
||||||
|
# LOADED describes that the plugin is currently loaded and is fully functional
|
||||||
|
LOADED = 1
|
||||||
|
|
||||||
|
|
||||||
|
class Pluginator:
|
||||||
|
""" The common plugin utilities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@contextmanager
|
||||||
|
def add_to_path(_p):
|
||||||
|
""" A Context Manager to add the provided path to Python's sys.path temporarily.
|
||||||
|
"""
|
||||||
|
import sys # pylint: disable=import-outside-toplevel
|
||||||
|
old_path = sys.path
|
||||||
|
sys.path = sys.path[:]
|
||||||
|
sys.path.insert(0, _p)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
sys.path = old_path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(folder, packageName, classType) -> List:
|
||||||
|
""" Returns Array of Plugin, each holding the plugin and the module it belongs to.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder (str): Path to the Directory.
|
||||||
|
packageName (str): Name of the package to import.
|
||||||
|
classType (desc.Node | BaseSubmitter): The base type of plugin which is being imported.
|
||||||
|
"""
|
||||||
|
pluginTypes = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# temporarily add folder to python path
|
||||||
|
with Pluginator.add_to_path(folder):
|
||||||
|
# import node package
|
||||||
|
package = importlib.import_module(packageName)
|
||||||
|
packageName = package.packageName if hasattr(package, 'packageName') else package.__name__
|
||||||
|
packageVersion = getattr(package, "__version__", None)
|
||||||
|
|
||||||
|
for importer, pluginName, ispkg in pkgutil.iter_modules(package.__path__):
|
||||||
|
pluginModuleName = '.' + pluginName
|
||||||
|
|
||||||
|
try:
|
||||||
|
pluginMod = importlib.import_module(pluginModuleName, package=package.__name__)
|
||||||
|
plugins = [plugin for name, plugin in inspect.getmembers(pluginMod, inspect.isclass)
|
||||||
|
if plugin.__module__ == '{}.{}'.format(package.__name__, pluginName)
|
||||||
|
and issubclass(plugin, classType)]
|
||||||
|
if not plugins:
|
||||||
|
logging.warning("No class defined in plugin: {}".format(pluginModuleName))
|
||||||
|
|
||||||
|
# Update the package name and version on the plugin
|
||||||
|
for p in plugins:
|
||||||
|
p.packageName = packageName
|
||||||
|
p.packageVersion = packageVersion
|
||||||
|
|
||||||
|
# Extend all of the plugins
|
||||||
|
pluginTypes.extend(plugins)
|
||||||
|
except Exception as exc:
|
||||||
|
errors.append(' * {}: {}'.format(pluginName, str(exc)))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
logging.warning('== The following "{package}" plugins could not be loaded ==\n'
|
||||||
|
'{errorMsg}\n'
|
||||||
|
.format(package=packageName, errorMsg='\n'.join(errors)))
|
||||||
|
|
||||||
|
return pluginTypes
|
283
meshroom/_plugins/node.py
Normal file
283
meshroom/_plugins/node.py
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
""" Node plugins.
|
||||||
|
"""
|
||||||
|
# Types
|
||||||
|
from typing import Dict, List
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
# STD
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
|
import pkgutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Internal
|
||||||
|
from meshroom.core import desc
|
||||||
|
|
||||||
|
# Plugins
|
||||||
|
from .base import Status, Pluginator
|
||||||
|
|
||||||
|
|
||||||
|
class NodeDescriptor(object):
|
||||||
|
""" Class to describe a Node Plugin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_DEFAULT_VERSION = "0"
|
||||||
|
|
||||||
|
def __init__(self, name: str, descriptor: desc.Node) -> None:
|
||||||
|
""" Constructor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the Node.
|
||||||
|
descriptor (desc.Node): The Node descriptor.
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Node descriptions
|
||||||
|
self._name: str = name
|
||||||
|
self._descriptor: desc.Node = descriptor
|
||||||
|
|
||||||
|
# Update the Node Descriptor's plugin
|
||||||
|
self._descriptor.plugin = self
|
||||||
|
|
||||||
|
# Module descriptions
|
||||||
|
self._module: ModuleType = sys.modules.get(self._descriptor.__module__)
|
||||||
|
self._version = getattr(self._module, "__version__", self._DEFAULT_VERSION)
|
||||||
|
self._path = self._module.__file__
|
||||||
|
|
||||||
|
self._errors: List[str] = self._validate()
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
name = property(lambda self: self._name)
|
||||||
|
descriptor = property(lambda self: self._descriptor)
|
||||||
|
category = property(lambda self: self._descriptor.category)
|
||||||
|
errors = property(lambda self: self._errors)
|
||||||
|
documentation = property(lambda self: self._descriptor.documentation)
|
||||||
|
version = property(lambda self: self._version)
|
||||||
|
path = property(lambda self: self._path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> Status:
|
||||||
|
""" Returns the status of the plugin.
|
||||||
|
"""
|
||||||
|
# If no errors -> then it is loaded and available
|
||||||
|
return Status(not self._errors)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
""" Represents the Instance.
|
||||||
|
"""
|
||||||
|
return f"NodeDescriptor::{self._name} at {hex(id(self))}"
|
||||||
|
|
||||||
|
def _validate(self) -> List[str]:
|
||||||
|
""" Check that the node has a valid description before being loaded. For the description to be valid, the
|
||||||
|
default value of every parameter needs to correspond to the type of the parameter.
|
||||||
|
|
||||||
|
An empty returned list means that every parameter is valid, and so is the node's description. If it is not
|
||||||
|
valid, the returned list contains the names of the invalid parameters. In case of nested parameters (parameters
|
||||||
|
in groups or lists, for example), the name of the parameter follows the name of the parent attributes.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
If the attribute "x", contained in group "group", is invalid, then it will be added to the list as "group:x".
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
errors (list<str>): the list of invalid parameters if there are any, empty list otherwise.
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for param in self._descriptor.inputs:
|
||||||
|
err = param.checkValueTypes()
|
||||||
|
if err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
for param in self._descriptor.outputs:
|
||||||
|
# Ignore the output attributes with None as the value
|
||||||
|
if param.value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
err = param.checkValueTypes()
|
||||||
|
if err:
|
||||||
|
errors.append(err)
|
||||||
|
|
||||||
|
# Return any errors while validating the input and output attributes
|
||||||
|
return errors
|
||||||
|
|
||||||
|
# Public
|
||||||
|
def reload(self) -> None:
|
||||||
|
""" Reloads the Node.
|
||||||
|
"""
|
||||||
|
# Reload the Module
|
||||||
|
updated = importlib.reload(self._module)
|
||||||
|
|
||||||
|
# Get the Descriptor
|
||||||
|
descriptor = getattr(updated, self._name)
|
||||||
|
|
||||||
|
# Cannot find the current class on the updated module ?
|
||||||
|
if not descriptor:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update the descriptor and call for validation
|
||||||
|
self._module = updated
|
||||||
|
|
||||||
|
self._descriptor = descriptor
|
||||||
|
self._descriptor.plugin = self
|
||||||
|
|
||||||
|
# Update the errors if any that may have been introduced
|
||||||
|
self._errors = self._validate()
|
||||||
|
|
||||||
|
|
||||||
|
class NodePluginManager(object):
|
||||||
|
""" A Singleton class Managing the Node plugins for Meshroom.
|
||||||
|
"""
|
||||||
|
# Static class instance to ensure we have only one created at all times
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
# The core class to which the Node plugins belong
|
||||||
|
_CLASS_TYPE = desc.Node
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
# Verify that the instance we have is of the current type
|
||||||
|
if not isinstance(cls._instance, cls):
|
||||||
|
# Create an instance to work with
|
||||||
|
cls._instance = object.__new__(cls)
|
||||||
|
|
||||||
|
# Init the class parameters
|
||||||
|
# The class parameters need to be initialised outside __init__ as when Cls() gets invoked __init__ gets
|
||||||
|
# called as well, so even when we get the same instance back, the params are updated for this and every
|
||||||
|
# other instance and that what will affect attrs in all places where the current instance is being used
|
||||||
|
cls._instance.init()
|
||||||
|
|
||||||
|
# Return the instantiated instance
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def init(self) -> None:
|
||||||
|
""" Constructor for members.
|
||||||
|
"""
|
||||||
|
self._descriptors: Dict[str: NodeDescriptor] = {} # pylint: disable=attribute-defined-outside-init
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
descriptors = property(lambda self: self._descriptors)
|
||||||
|
|
||||||
|
# Public
|
||||||
|
def registered(self, name: str) -> bool:
|
||||||
|
""" Returns whether the plugin has been registered already or not.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the plugin.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool. True if the plugin was registered, else False.
|
||||||
|
"""
|
||||||
|
return name in self._descriptors
|
||||||
|
|
||||||
|
def status(self, name: str) -> Status:
|
||||||
|
""" Returns the current status of the plugin.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the plugin.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Status. The current status of the plugin.
|
||||||
|
"""
|
||||||
|
# Fetch the plugin Descriptor
|
||||||
|
plugin = self._descriptors.get(name)
|
||||||
|
|
||||||
|
if not plugin:
|
||||||
|
return Status.UNLOADED
|
||||||
|
|
||||||
|
# Return the status from the plugin itself
|
||||||
|
return plugin.status
|
||||||
|
|
||||||
|
def errors(self, name) -> List[str]:
|
||||||
|
""" Returns the Errors on the plugins if there are any.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the plugin.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list<str>. the list of invalid parameters if there are any, empty list otherwise.
|
||||||
|
"""
|
||||||
|
# Fetch the plugin Descriptor
|
||||||
|
plugin = self._descriptors.get(name)
|
||||||
|
|
||||||
|
if not plugin:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Return any errors from the plugin side
|
||||||
|
return plugin.errors
|
||||||
|
|
||||||
|
def registerNode(self, descriptor: desc.Node) -> bool:
|
||||||
|
""" Registers a Node into Meshroom.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
descriptor (desc.Node): The Node descriptor.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool. Returns True if the node is registered. False if it is already registered.
|
||||||
|
"""
|
||||||
|
# Plugin name
|
||||||
|
name = descriptor.__name__
|
||||||
|
|
||||||
|
# Already registered ?
|
||||||
|
if self.registered(name):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Register it
|
||||||
|
self.register(name, descriptor)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def unregisterNode(self, descriptor: desc.Node) -> None:
|
||||||
|
""" Unregisters the Node from the Registered Set of Nodes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
descriptor (desc.Node): The Node descriptor.
|
||||||
|
"""
|
||||||
|
# Plugin name
|
||||||
|
name = descriptor.__name__
|
||||||
|
|
||||||
|
# Ensure that we have this node already present
|
||||||
|
assert name in self._descriptors
|
||||||
|
# Delete the instance
|
||||||
|
del self._descriptors[name]
|
||||||
|
|
||||||
|
def register(self, name: str, descriptor: desc.Node) -> None:
|
||||||
|
""" Registers a Node within meshroom.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the Node Plugin.
|
||||||
|
descriptor (desc.Node): The Node descriptor.
|
||||||
|
"""
|
||||||
|
self._descriptors[name] = NodeDescriptor(name, descriptor)
|
||||||
|
|
||||||
|
def descriptor(self, name: str) -> desc.Node:
|
||||||
|
""" Returns the Node Desc for the provided name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the plugin.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
desc.Node. The Node Desc instance.
|
||||||
|
"""
|
||||||
|
# Returns the plugin for the provided name
|
||||||
|
plugin = self._descriptors.get(name)
|
||||||
|
|
||||||
|
# Plugin not found with the name
|
||||||
|
if not plugin:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Return the Node Descriptor for the plugin
|
||||||
|
return plugin.descriptor
|
||||||
|
|
||||||
|
def load(self, directory) -> None:
|
||||||
|
""" Loads Node from the provided directory.
|
||||||
|
"""
|
||||||
|
for _, package, ispkg in pkgutil.walk_packages([directory]):
|
||||||
|
if not ispkg:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the plugins from the provided directory and the python package
|
||||||
|
descriptors = Pluginator.get(directory, package, self._CLASS_TYPE)
|
||||||
|
|
||||||
|
for descriptor in descriptors:
|
||||||
|
self.registerNode(descriptor)
|
||||||
|
|
||||||
|
logging.debug('Nodes loaded [{}]: {}'.format(package, ', '.join([d.__name__ for d in descriptors])))
|
|
@ -1,12 +1,8 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from contextlib import contextmanager
|
|
||||||
import importlib
|
|
||||||
import inspect
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import pkgutil
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -19,7 +15,7 @@ except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from meshroom.core.submitter import BaseSubmitter
|
from meshroom.core.submitter import BaseSubmitter
|
||||||
from . import desc
|
from meshroom import _plugins
|
||||||
|
|
||||||
# Setup logging
|
# Setup logging
|
||||||
logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=logging.INFO)
|
logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=logging.INFO)
|
||||||
|
@ -29,10 +25,15 @@ sessionUid = str(uuid.uuid1())
|
||||||
|
|
||||||
cacheFolderName = 'MeshroomCache'
|
cacheFolderName = 'MeshroomCache'
|
||||||
defaultCacheFolder = os.environ.get('MESHROOM_CACHE', os.path.join(tempfile.gettempdir(), cacheFolderName))
|
defaultCacheFolder = os.environ.get('MESHROOM_CACHE', os.path.join(tempfile.gettempdir(), cacheFolderName))
|
||||||
nodesDesc = {}
|
|
||||||
submitters = {}
|
submitters = {}
|
||||||
pipelineTemplates = {}
|
pipelineTemplates = {}
|
||||||
|
|
||||||
|
# Manages plugins for Meshroom Nodes
|
||||||
|
pluginManager = _plugins.NodePluginManager()
|
||||||
|
|
||||||
|
# Plugin States
|
||||||
|
PluginStatus = _plugins.Status
|
||||||
|
|
||||||
|
|
||||||
def hashValue(value):
|
def hashValue(value):
|
||||||
""" Hash 'value' using sha1. """
|
""" Hash 'value' using sha1. """
|
||||||
|
@ -40,100 +41,6 @@ def hashValue(value):
|
||||||
return hashObject.hexdigest()
|
return hashObject.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def add_to_path(p):
|
|
||||||
import sys
|
|
||||||
old_path = sys.path
|
|
||||||
sys.path = sys.path[:]
|
|
||||||
sys.path.insert(0, p)
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
sys.path = old_path
|
|
||||||
|
|
||||||
|
|
||||||
def loadPlugins(folder, packageName, classType):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
pluginTypes = []
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# temporarily add folder to python path
|
|
||||||
with add_to_path(folder):
|
|
||||||
# import node package
|
|
||||||
package = importlib.import_module(packageName)
|
|
||||||
packageName = package.packageName if hasattr(package, 'packageName') else package.__name__
|
|
||||||
packageVersion = getattr(package, "__version__", None)
|
|
||||||
|
|
||||||
for importer, pluginName, ispkg in pkgutil.iter_modules(package.__path__):
|
|
||||||
pluginModuleName = '.' + pluginName
|
|
||||||
|
|
||||||
try:
|
|
||||||
pluginMod = importlib.import_module(pluginModuleName, package=package.__name__)
|
|
||||||
plugins = [plugin for name, plugin in inspect.getmembers(pluginMod, inspect.isclass)
|
|
||||||
if plugin.__module__ == '{}.{}'.format(package.__name__, pluginName)
|
|
||||||
and issubclass(plugin, classType)]
|
|
||||||
if not plugins:
|
|
||||||
logging.warning("No class defined in plugin: {}".format(pluginModuleName))
|
|
||||||
|
|
||||||
importPlugin = True
|
|
||||||
for p in plugins:
|
|
||||||
if classType == desc.Node:
|
|
||||||
nodeErrors = validateNodeDesc(p)
|
|
||||||
if nodeErrors:
|
|
||||||
errors.append(" * {}: The following parameters do not have valid default values/ranges: {}"
|
|
||||||
.format(pluginName, ", ".join(nodeErrors)))
|
|
||||||
importPlugin = False
|
|
||||||
break
|
|
||||||
p.packageName = packageName
|
|
||||||
p.packageVersion = packageVersion
|
|
||||||
if importPlugin:
|
|
||||||
pluginTypes.extend(plugins)
|
|
||||||
except Exception as e:
|
|
||||||
errors.append(' * {}: {}'.format(pluginName, str(e)))
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
logging.warning('== The following "{package}" plugins could not be loaded ==\n'
|
|
||||||
'{errorMsg}\n'
|
|
||||||
.format(package=packageName, errorMsg='\n'.join(errors)))
|
|
||||||
return pluginTypes
|
|
||||||
|
|
||||||
|
|
||||||
def validateNodeDesc(nodeDesc):
|
|
||||||
"""
|
|
||||||
Check that the node has a valid description before being loaded. For the description
|
|
||||||
to be valid, the default value of every parameter needs to correspond to the type
|
|
||||||
of the parameter.
|
|
||||||
An empty returned list means that every parameter is valid, and so is the node's description.
|
|
||||||
If it is not valid, the returned list contains the names of the invalid parameters. In case
|
|
||||||
of nested parameters (parameters in groups or lists, for example), the name of the parameter
|
|
||||||
follows the name of the parent attributes. For example, if the attribute "x", contained in group
|
|
||||||
"group", is invalid, then it will be added to the list as "group:x".
|
|
||||||
|
|
||||||
Args:
|
|
||||||
nodeDesc (desc.Node): description of the node
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
errors (list): the list of invalid parameters if there are any, empty list otherwise
|
|
||||||
"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for param in nodeDesc.inputs:
|
|
||||||
err = param.checkValueTypes()
|
|
||||||
if err:
|
|
||||||
errors.append(err)
|
|
||||||
|
|
||||||
for param in nodeDesc.outputs:
|
|
||||||
if param.value is None:
|
|
||||||
continue
|
|
||||||
err = param.checkValueTypes()
|
|
||||||
if err:
|
|
||||||
errors.append(err)
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
class Version(object):
|
class Version(object):
|
||||||
"""
|
"""
|
||||||
Version provides convenient properties and methods to manipulate and compare versions.
|
Version provides convenient properties and methods to manipulate and compare versions.
|
||||||
|
@ -149,7 +56,10 @@ class Version(object):
|
||||||
self.status = str()
|
self.status = str()
|
||||||
elif len(args) == 1:
|
elif len(args) == 1:
|
||||||
versionName = args[0]
|
versionName = args[0]
|
||||||
if isinstance(versionName, str):
|
if not versionName: # If this was initialised with Version(None) or Version("")
|
||||||
|
self.components = tuple()
|
||||||
|
self.status = str()
|
||||||
|
elif isinstance(versionName, str):
|
||||||
self.components, self.status = Version.toComponents(versionName)
|
self.components, self.status = Version.toComponents(versionName)
|
||||||
elif isinstance(versionName, (list, tuple)):
|
elif isinstance(versionName, (list, tuple)):
|
||||||
self.components = tuple([int(v) for v in versionName])
|
self.components = tuple([int(v) for v in versionName])
|
||||||
|
@ -278,36 +188,36 @@ def nodeVersion(nodeDesc, default=None):
|
||||||
return moduleVersion(nodeDesc.__module__, default)
|
return moduleVersion(nodeDesc.__module__, default)
|
||||||
|
|
||||||
|
|
||||||
def registerNodeType(nodeType):
|
def registerNodeType(nodeType, module=None):
|
||||||
""" Register a Node Type based on a Node Description class.
|
""" Register a Node Type based on a Node Description class.
|
||||||
|
|
||||||
After registration, nodes of this type can be instantiated in a Graph.
|
After registration, nodes of this type can be instantiated in a Graph.
|
||||||
"""
|
"""
|
||||||
global nodesDesc
|
# Register the node in plugin manager
|
||||||
if nodeType.__name__ in nodesDesc:
|
registered = pluginManager.registerNode(nodeType, module=module)
|
||||||
logging.error("Node Desc {} is already registered.".format(nodeType.__name__))
|
|
||||||
nodesDesc[nodeType.__name__] = nodeType
|
# The plugin was already registered
|
||||||
|
if not registered:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Plugin Name
|
||||||
|
name = nodeType.__name__
|
||||||
|
|
||||||
|
# Check the status of the plugin to identify if we have any errors on it while loading ?
|
||||||
|
if pluginManager.status(name) == PluginStatus.ERRORED:
|
||||||
|
errors = ", ".join(pluginManager.errors(name))
|
||||||
|
logging.warning(f"[PluginManager] {name}: The following parameters do not have valid default values/ranges: {errors}.")
|
||||||
|
|
||||||
|
|
||||||
def unregisterNodeType(nodeType):
|
def unregisterNodeType(nodeType):
|
||||||
""" Remove 'nodeType' from the list of register node types. """
|
""" Remove 'nodeType' from the list of register node types. """
|
||||||
global nodesDesc
|
# Unregister the node from plugin manager
|
||||||
assert nodeType.__name__ in nodesDesc
|
pluginManager.unregisterNode(nodeType)
|
||||||
del nodesDesc[nodeType.__name__]
|
|
||||||
|
|
||||||
|
|
||||||
def loadNodes(folder, packageName):
|
|
||||||
return loadPlugins(folder, packageName, desc.Node)
|
|
||||||
|
|
||||||
|
|
||||||
def loadAllNodes(folder):
|
def loadAllNodes(folder):
|
||||||
global nodesDesc
|
# Load plugins from the node's plugin manager
|
||||||
for importer, package, ispkg in pkgutil.walk_packages([folder]):
|
pluginManager.load(folder)
|
||||||
if ispkg:
|
|
||||||
nodeTypes = loadNodes(folder, package)
|
|
||||||
for nodeType in nodeTypes:
|
|
||||||
registerNodeType(nodeType)
|
|
||||||
logging.debug('Nodes loaded [{}]: {}'.format(package, ', '.join([nodeType.__name__ for nodeType in nodeTypes])))
|
|
||||||
|
|
||||||
|
|
||||||
def registerSubmitter(s):
|
def registerSubmitter(s):
|
||||||
|
@ -318,7 +228,7 @@ def registerSubmitter(s):
|
||||||
|
|
||||||
|
|
||||||
def loadSubmitters(folder, packageName):
|
def loadSubmitters(folder, packageName):
|
||||||
return loadPlugins(folder, packageName, BaseSubmitter)
|
return _plugins.Pluginator.get(folder, packageName, BaseSubmitter)
|
||||||
|
|
||||||
|
|
||||||
def loadPipelineTemplates(folder):
|
def loadPipelineTemplates(folder):
|
||||||
|
|
|
@ -16,7 +16,7 @@ from meshroom.common import BaseObject, DictModel, Slot, Signal, Property
|
||||||
from meshroom.core import Version
|
from meshroom.core import Version
|
||||||
from meshroom.core.attribute import Attribute, ListAttribute, GroupAttribute
|
from meshroom.core.attribute import Attribute, ListAttribute, GroupAttribute
|
||||||
from meshroom.core.exception import GraphCompatibilityError, StopGraphVisit, StopBranchVisit
|
from meshroom.core.exception import GraphCompatibilityError, StopGraphVisit, StopBranchVisit
|
||||||
from meshroom.core.node import nodeFactory, Status, Node, CompatibilityNode
|
from meshroom.core.node import nodeFactory, Status, Node, CompatibilityNode, IncompatiblePluginNode
|
||||||
|
|
||||||
# Replace default encoder to support Enums
|
# Replace default encoder to support Enums
|
||||||
|
|
||||||
|
@ -444,7 +444,7 @@ class Graph(BaseObject):
|
||||||
# Second pass to update all the links in the input/output attributes for every node with the new names
|
# Second pass to update all the links in the input/output attributes for every node with the new names
|
||||||
for nodeName, nodeData in updatedData.items():
|
for nodeName, nodeData in updatedData.items():
|
||||||
nodeType = nodeData.get("nodeType", None)
|
nodeType = nodeData.get("nodeType", None)
|
||||||
nodeDesc = meshroom.core.nodesDesc[nodeType]
|
nodeDesc = meshroom.core.pluginManager.descriptor(nodeType)
|
||||||
|
|
||||||
inputs = nodeData.get("inputs", {})
|
inputs = nodeData.get("inputs", {})
|
||||||
outputs = nodeData.get("outputs", {})
|
outputs = nodeData.get("outputs", {})
|
||||||
|
@ -762,7 +762,14 @@ 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, position=position, **kwargs), uniqueName=name)
|
# The Node Type to Construcct
|
||||||
|
Type = Node
|
||||||
|
|
||||||
|
if meshroom.core.pluginManager.status(nodeType) == meshroom.core.PluginStatus.ERRORED:
|
||||||
|
Type = IncompatiblePluginNode
|
||||||
|
|
||||||
|
# Construct the Node
|
||||||
|
n = self.addNode(Type(nodeType, position=position, **kwargs), uniqueName=name)
|
||||||
n.updateInternals()
|
n.updateInternals()
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
|
# Types
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -495,9 +498,9 @@ class BaseNode(BaseObject):
|
||||||
self._nodeType = nodeType
|
self._nodeType = nodeType
|
||||||
self.nodeDesc = None
|
self.nodeDesc = None
|
||||||
|
|
||||||
# instantiate node description if nodeType is valid
|
# instantiate node description if nodeType has been registered
|
||||||
if nodeType in meshroom.core.nodesDesc:
|
if meshroom.core.pluginManager.registered(nodeType):
|
||||||
self.nodeDesc = meshroom.core.nodesDesc[nodeType]()
|
self.nodeDesc = meshroom.core.pluginManager.descriptor(nodeType)()
|
||||||
|
|
||||||
self.packageName = self.packageVersion = ""
|
self.packageName = self.packageVersion = ""
|
||||||
self._internalFolder = ""
|
self._internalFolder = ""
|
||||||
|
@ -1593,6 +1596,7 @@ class CompatibilityIssue(Enum):
|
||||||
VersionConflict = 2 # mismatch between node's description version and serialized node data
|
VersionConflict = 2 # mismatch between node's description version and serialized node data
|
||||||
DescriptionConflict = 3 # mismatch between node's description attributes and serialized node data
|
DescriptionConflict = 3 # mismatch between node's description attributes and serialized node data
|
||||||
UidConflict = 4 # mismatch between computed UIDs and UIDs stored in serialized node data
|
UidConflict = 4 # mismatch between computed UIDs and UIDs stored in serialized node data
|
||||||
|
PluginIssue = 5 # Issue with interpreting the plugin due to any issues with interpreting the plugin
|
||||||
|
|
||||||
|
|
||||||
class CompatibilityNode(BaseNode):
|
class CompatibilityNode(BaseNode):
|
||||||
|
@ -1754,8 +1758,9 @@ class CompatibilityNode(BaseNode):
|
||||||
self._attributes.add(attribute)
|
self._attributes.add(attribute)
|
||||||
return matchDesc
|
return matchDesc
|
||||||
|
|
||||||
@property
|
def _issueDetails(self) -> str:
|
||||||
def issueDetails(self):
|
""" Returns Issue Details.
|
||||||
|
"""
|
||||||
if self.issue == CompatibilityIssue.UnknownNodeType:
|
if self.issue == CompatibilityIssue.UnknownNodeType:
|
||||||
return "Unknown node type: '{}'.".format(self.nodeType)
|
return "Unknown node type: '{}'.".format(self.nodeType)
|
||||||
elif self.issue == CompatibilityIssue.VersionConflict:
|
elif self.issue == CompatibilityIssue.VersionConflict:
|
||||||
|
@ -1766,9 +1771,15 @@ class CompatibilityNode(BaseNode):
|
||||||
return "Node attributes do not match node description."
|
return "Node attributes do not match node description."
|
||||||
elif self.issue == CompatibilityIssue.UidConflict:
|
elif self.issue == CompatibilityIssue.UidConflict:
|
||||||
return "Node UID differs from the expected one."
|
return "Node UID differs from the expected one."
|
||||||
else:
|
elif self.issue == CompatibilityIssue.PluginIssue:
|
||||||
|
return "Error interpreting the Node Plugin."
|
||||||
|
|
||||||
return "Unknown error."
|
return "Unknown error."
|
||||||
|
|
||||||
|
@property
|
||||||
|
def issueDetails(self) -> str:
|
||||||
|
return self._issueDetails()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inputs(self):
|
def inputs(self):
|
||||||
""" Get current node inputs, where links could differ from original serialized node data
|
""" Get current node inputs, where links could differ from original serialized node data
|
||||||
|
@ -1852,6 +1863,74 @@ class CompatibilityNode(BaseNode):
|
||||||
issueDetails = Property(str, issueDetails.fget, constant=True)
|
issueDetails = Property(str, issueDetails.fget, constant=True)
|
||||||
|
|
||||||
|
|
||||||
|
class IncompatiblePluginNode(CompatibilityNode):
|
||||||
|
""" Fallback BaseNode subclass to instantiate Nodes having compatibility issues with current type description.
|
||||||
|
CompatibilityNode creates an 'empty-shell' exposing the deserialized node as-is,
|
||||||
|
with all its inputs and precomputed outputs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, nodeType, position=None, issue=CompatibilityIssue.PluginIssue, parent=None, **kwargs):
|
||||||
|
super(IncompatiblePluginNode, self).__init__(nodeType, {}, position=position, issue=issue, parent=parent)
|
||||||
|
|
||||||
|
self.packageName = self.nodeDesc.packageName
|
||||||
|
self.packageVersion = self.nodeDesc.packageVersion
|
||||||
|
self._internalFolder = self.nodeDesc.internalFolder
|
||||||
|
|
||||||
|
# Fetch the errors for the plugin
|
||||||
|
self._nodeErrors = self.nodeDesc.plugin.errors
|
||||||
|
|
||||||
|
# Add the Attributes
|
||||||
|
for attrDesc in self.nodeDesc.inputs:
|
||||||
|
# Don't add any invalid attributes
|
||||||
|
if attrDesc.invalid:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=False, node=self))
|
||||||
|
|
||||||
|
for attrDesc in self.nodeDesc.outputs:
|
||||||
|
# Don't add any invalid attributes
|
||||||
|
if attrDesc.invalid:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=True, node=self))
|
||||||
|
|
||||||
|
for attrDesc in self.nodeDesc.internalInputs:
|
||||||
|
# Don't add any invalid attributes
|
||||||
|
if attrDesc.invalid:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._internalAttributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=False,
|
||||||
|
node=self))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def issueDetails(self) -> str:
|
||||||
|
""" Returns any issue details for the Node.
|
||||||
|
"""
|
||||||
|
# The basic issue detail
|
||||||
|
details = [self._issueDetails()]
|
||||||
|
|
||||||
|
# Add the Node Parametric error details
|
||||||
|
details.extend(self._errorDetails())
|
||||||
|
|
||||||
|
return "\n".join(details)
|
||||||
|
|
||||||
|
# Protected
|
||||||
|
def _errorDetails(self) -> List[str]:
|
||||||
|
""" Returns the details of the Parametric errors on the Node.
|
||||||
|
"""
|
||||||
|
errors = ["Following parameters have invalid default values/ranges:"]
|
||||||
|
|
||||||
|
# Add the parameters from the node Errors
|
||||||
|
errors.extend([f"* Param {param}" for param in self._nodeErrors])
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# An Incompatible Plguin Node should not be upgraded but only reloaded
|
||||||
|
canUpgrade = Property(bool, lambda _: False, constant=True)
|
||||||
|
issueDetails = Property(str, issueDetails.fget, constant=True)
|
||||||
|
|
||||||
|
|
||||||
def nodeFactory(nodeDict, name=None, template=False, uidConflict=False):
|
def nodeFactory(nodeDict, name=None, template=False, uidConflict=False):
|
||||||
"""
|
"""
|
||||||
Create a node instance by deserializing the given node data.
|
Create a node instance by deserializing the given node data.
|
||||||
|
@ -1885,11 +1964,11 @@ def nodeFactory(nodeDict, name=None, template=False, uidConflict=False):
|
||||||
|
|
||||||
compatibilityIssue = None
|
compatibilityIssue = None
|
||||||
|
|
||||||
nodeDesc = None
|
# Returns the desc.Node inherited class or None if the plugin was not registered
|
||||||
try:
|
nodeDesc = meshroom.core.pluginManager.descriptor(nodeType)
|
||||||
nodeDesc = meshroom.core.nodesDesc[nodeType]
|
|
||||||
except KeyError:
|
# Node plugin was not registered
|
||||||
# Unknown node type
|
if not nodeDesc:
|
||||||
compatibilityIssue = CompatibilityIssue.UnknownNodeType
|
compatibilityIssue = CompatibilityIssue.UnknownNodeType
|
||||||
|
|
||||||
# Unknown node type should take precedence over UID conflict, as it cannot be resolved
|
# Unknown node type should take precedence over UID conflict, as it cannot be resolved
|
||||||
|
|
|
@ -11,7 +11,7 @@ from PySide6.QtGui import QIcon
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
import meshroom
|
import meshroom
|
||||||
from meshroom.core import nodesDesc
|
from meshroom.core import pluginManager
|
||||||
from meshroom.core.taskManager import TaskManager
|
from meshroom.core.taskManager import TaskManager
|
||||||
from meshroom.common import Property, Variant, Signal, Slot
|
from meshroom.common import Property, Variant, Signal, Slot
|
||||||
|
|
||||||
|
@ -240,6 +240,7 @@ class MeshroomApp(QApplication):
|
||||||
components.registerTypes()
|
components.registerTypes()
|
||||||
|
|
||||||
# expose available node types that can be instantiated
|
# expose available node types that can be instantiated
|
||||||
|
nodesDesc = pluginManager.descriptors
|
||||||
self.engine.rootContext().setContextProperty("_nodeTypes", {n: {"category": nodesDesc[n].category} for n in sorted(nodesDesc.keys())})
|
self.engine.rootContext().setContextProperty("_nodeTypes", {n: {"category": nodesDesc[n].category} for n in sorted(nodesDesc.keys())})
|
||||||
|
|
||||||
# instantiate Reconstruction object
|
# instantiate Reconstruction object
|
||||||
|
|
|
@ -496,7 +496,8 @@ class Reconstruction(UIGraph):
|
||||||
# Create all possible entries
|
# Create all possible entries
|
||||||
for category, _ in self.activeNodeCategories.items():
|
for category, _ in self.activeNodeCategories.items():
|
||||||
self._activeNodes.add(ActiveNode(category, parent=self))
|
self._activeNodes.add(ActiveNode(category, parent=self))
|
||||||
for nodeType, _ in meshroom.core.nodesDesc.items():
|
|
||||||
|
for nodeType in meshroom.core.pluginManager.descriptors:
|
||||||
self._activeNodes.add(ActiveNode(nodeType, parent=self))
|
self._activeNodes.add(ActiveNode(nodeType, parent=self))
|
||||||
|
|
||||||
def clearActiveNodes(self):
|
def clearActiveNodes(self):
|
||||||
|
@ -648,7 +649,9 @@ class Reconstruction(UIGraph):
|
||||||
if not sfmFile or not os.path.isfile(sfmFile):
|
if not sfmFile or not os.path.isfile(sfmFile):
|
||||||
self.tempCameraInit = None
|
self.tempCameraInit = None
|
||||||
return
|
return
|
||||||
nodeDesc = meshroom.core.nodesDesc["CameraInit"]()
|
|
||||||
|
# The camera init node should always exist
|
||||||
|
nodeDesc = meshroom.core.pluginManager.descriptor("CameraInit")()
|
||||||
views, intrinsics = nodeDesc.readSfMData(sfmFile)
|
views, intrinsics = nodeDesc.readSfMData(sfmFile)
|
||||||
tmpCameraInit = Node("CameraInit", viewpoints=views, intrinsics=intrinsics)
|
tmpCameraInit = Node("CameraInit", viewpoints=views, intrinsics=intrinsics)
|
||||||
tmpCameraInit.locked = True
|
tmpCameraInit.locked = True
|
||||||
|
|
|
@ -197,7 +197,7 @@ def test_description_conflict():
|
||||||
Test compatibility behavior for conflicting node descriptions.
|
Test compatibility behavior for conflicting node descriptions.
|
||||||
"""
|
"""
|
||||||
# copy registered node types to be able to restore them
|
# copy registered node types to be able to restore them
|
||||||
originalNodeTypes = copy.copy(meshroom.core.nodesDesc)
|
originalNodeTypes = copy.copy(meshroom.core.pluginManager.descriptors)
|
||||||
|
|
||||||
nodeTypes = [SampleNodeV1, SampleNodeV2, SampleNodeV3, SampleNodeV4, SampleNodeV5]
|
nodeTypes = [SampleNodeV1, SampleNodeV2, SampleNodeV3, SampleNodeV4, SampleNodeV5]
|
||||||
nodes = []
|
nodes = []
|
||||||
|
@ -224,7 +224,7 @@ def test_description_conflict():
|
||||||
# offset node types register to create description conflicts
|
# offset node types register to create description conflicts
|
||||||
# each node type name now reference the next one's implementation
|
# each node type name now reference the next one's implementation
|
||||||
for i, nt in enumerate(nodeTypes[:-1]):
|
for i, nt in enumerate(nodeTypes[:-1]):
|
||||||
meshroom.core.nodesDesc[nt.__name__] = nodeTypes[i+1]
|
meshroom.core.pluginManager.register(nt.__name__, nodeTypes[i+1])
|
||||||
|
|
||||||
# reload file
|
# reload file
|
||||||
g = loadGraph(graphFile)
|
g = loadGraph(graphFile)
|
||||||
|
@ -306,7 +306,7 @@ def test_description_conflict():
|
||||||
raise ValueError("Unexpected node type: " + srcNode.nodeType)
|
raise ValueError("Unexpected node type: " + srcNode.nodeType)
|
||||||
|
|
||||||
# restore original node types
|
# restore original node types
|
||||||
meshroom.core.nodesDesc = originalNodeTypes
|
meshroom.core.pluginManager._descriptors = originalNodeTypes # pylint: disable=protected-access
|
||||||
|
|
||||||
|
|
||||||
def test_upgradeAllNodes():
|
def test_upgradeAllNodes():
|
||||||
|
@ -331,8 +331,8 @@ def test_upgradeAllNodes():
|
||||||
unregisterNodeType(SampleNodeV2)
|
unregisterNodeType(SampleNodeV2)
|
||||||
unregisterNodeType(SampleInputNodeV2)
|
unregisterNodeType(SampleInputNodeV2)
|
||||||
# replace SampleNodeV1 by SampleNodeV2 and SampleInputNodeV1 by SampleInputNodeV2
|
# replace SampleNodeV1 by SampleNodeV2 and SampleInputNodeV1 by SampleInputNodeV2
|
||||||
meshroom.core.nodesDesc[SampleNodeV1.__name__] = SampleNodeV2
|
meshroom.core.pluginManager.register(SampleNodeV1.__name__, SampleNodeV2)
|
||||||
meshroom.core.nodesDesc[SampleInputNodeV1.__name__] = SampleInputNodeV2
|
meshroom.core.pluginManager.register(SampleInputNodeV1.__name__, SampleInputNodeV2)
|
||||||
|
|
||||||
# reload file
|
# reload file
|
||||||
g = loadGraph(graphFile)
|
g = loadGraph(graphFile)
|
||||||
|
@ -369,7 +369,7 @@ def test_conformUpgrade():
|
||||||
g.save(graphFile)
|
g.save(graphFile)
|
||||||
|
|
||||||
# replace SampleNodeV5 by SampleNodeV6
|
# replace SampleNodeV5 by SampleNodeV6
|
||||||
meshroom.core.nodesDesc[SampleNodeV5.__name__] = SampleNodeV6
|
meshroom.core.pluginManager.register(SampleNodeV5.__name__, SampleNodeV6)
|
||||||
|
|
||||||
# reload file
|
# reload file
|
||||||
g = loadGraph(graphFile)
|
g = loadGraph(graphFile)
|
||||||
|
|
67
tests/test_plugins.py
Normal file
67
tests/test_plugins.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
""" Test for Meshroom Plugins.
|
||||||
|
"""
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# coding:utf-8
|
||||||
|
|
||||||
|
from meshroom.core import _plugins
|
||||||
|
from meshroom.core import desc, registerNodeType, unregisterNodeType
|
||||||
|
|
||||||
|
|
||||||
|
class SampleNode(desc.Node):
|
||||||
|
""" Sample Node for unit testing """
|
||||||
|
|
||||||
|
category = "Sample"
|
||||||
|
|
||||||
|
inputs = [
|
||||||
|
desc.File(name='input', label='Input', description='', value='',),
|
||||||
|
desc.StringParam(name='paramA', label='ParamA', description='', value='', invalidate=False) # No impact on UID
|
||||||
|
]
|
||||||
|
outputs = [
|
||||||
|
desc.File(name='output', label='Output', description='', value=desc.Node.internalFolder)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_management():
|
||||||
|
""" Tests the plugin manager for registering and unregistering node.
|
||||||
|
"""
|
||||||
|
# Sample Node name
|
||||||
|
name = SampleNode.__name__
|
||||||
|
|
||||||
|
# Register the node
|
||||||
|
registerNodeType(SampleNode)
|
||||||
|
|
||||||
|
# Since the Node Plugin Manager is a singleton instance
|
||||||
|
# We should still be able to instantiate and have a look at out registered plugins directly
|
||||||
|
pluginManager = _plugins.NodePluginManager()
|
||||||
|
|
||||||
|
# Assert that the plugin we have registered above is indeed registered
|
||||||
|
assert pluginManager.registered(name)
|
||||||
|
|
||||||
|
# Assert that the plugin can only be registered once
|
||||||
|
assert not pluginManager.registerNode(SampleNode)
|
||||||
|
|
||||||
|
# And once un-registered, it should no longer be present in the pluginManager
|
||||||
|
unregisterNodeType(SampleNode)
|
||||||
|
|
||||||
|
# Assert that the plugin we have registered above is indeed registered
|
||||||
|
assert not pluginManager.registered(name)
|
||||||
|
assert name not in pluginManager.descriptors
|
||||||
|
|
||||||
|
def test_descriptor():
|
||||||
|
""" Tests the Descriptor and NodeDescriptor instances.
|
||||||
|
"""
|
||||||
|
# Register the node
|
||||||
|
registerNodeType(SampleNode)
|
||||||
|
|
||||||
|
# Since the Node Plugin Manager is a singleton instance
|
||||||
|
# We should still be able to instantiate and have a look at out registered plugins directly
|
||||||
|
pluginManager = _plugins.NodePluginManager()
|
||||||
|
|
||||||
|
# Assert the descriptor is same as the Plugin NodeType
|
||||||
|
assert pluginManager.descriptor(SampleNode.__name__).__name__ == SampleNode.__name__
|
||||||
|
|
||||||
|
# Assert that the category of the NodeDescriptor is correct for the registered plugin
|
||||||
|
assert pluginManager.descriptors.get(SampleNode.__name__).category == "Sample"
|
||||||
|
|
||||||
|
# Finally unregister the plugin
|
||||||
|
unregisterNodeType(SampleNode)
|
|
@ -34,9 +34,10 @@ def test_templateVersions():
|
||||||
|
|
||||||
for _, nodeData in graphData.items():
|
for _, nodeData in graphData.items():
|
||||||
nodeType = nodeData["nodeType"]
|
nodeType = nodeData["nodeType"]
|
||||||
assert nodeType in meshroom.core.nodesDesc
|
# Assert that the plugin (nodeType) is indeed registered to be used
|
||||||
|
assert meshroom.core.pluginManager.registered(nodeType)
|
||||||
|
|
||||||
nodeDesc = meshroom.core.nodesDesc[nodeType]
|
nodeDesc = meshroom.core.pluginManager.descriptor(nodeType)
|
||||||
currentNodeVersion = meshroom.core.nodeVersion(nodeDesc)
|
currentNodeVersion = meshroom.core.nodeVersion(nodeDesc)
|
||||||
|
|
||||||
inputs = nodeData.get("inputs", {})
|
inputs = nodeData.get("inputs", {})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue