1919except Exception :
2020 pass
2121
22+ from meshroom .core .plugins import NodePlugin , NodePluginManager , Plugin , ProcessEnv
2223from meshroom .core .submitter import BaseSubmitter
2324from meshroom .env import EnvVar , meshroomFolder
2425from . import desc
3132sessionUid = str (uuid .uuid1 ())
3233
3334cacheFolderName = 'MeshroomCache'
34- nodesDesc : dict [ str , desc . BaseNode ] = {}
35+ pluginManager : NodePluginManager = NodePluginManager ()
3536submitters : dict [str , BaseSubmitter ] = {}
3637pipelineTemplates : dict [str , str ] = {}
3738
@@ -41,7 +42,6 @@ def hashValue(value) -> str:
4142 hashObject = hashlib .sha1 (str (value ).encode ('utf-8' ))
4243 return hashObject .hexdigest ()
4344
44-
4545@contextmanager
4646def add_to_path (p ):
4747 import sys
@@ -53,9 +53,15 @@ def add_to_path(p):
5353 finally :
5454 sys .path = old_path
5555
56-
57- def loadClasses (folder , packageName , classType ):
56+ def loadClasses (folder : str , packageName : str , classType : type ) -> list [type ]:
5857 """
58+ Go over the Python module named "packageName" located in "folder" to find files
59+ that contain classes of type "classType" and return these classes in a list.
60+
61+ Args:
62+ folder: the folder to load the module from.
63+ packageName: the name of the module to look for nodes in.
64+ classType: the class to look for in the files that are inspected.
5965 """
6066 classes = []
6167 errors = []
@@ -67,7 +73,8 @@ def loadClasses(folder, packageName, classType):
6773
6874 try :
6975 package = importlib .import_module (packageName )
70- packageName = package .packageName if hasattr (package , 'packageName' ) else package .__name__
76+ packageName = package .packageName if hasattr (package , "packageName" ) \
77+ else package .__name__
7178 packageVersion = getattr (package , "__version__" , None )
7279 packagePath = os .path .dirname (package .__file__ )
7380 except Exception as e :
@@ -83,31 +90,34 @@ def loadClasses(folder, packageName, classType):
8390 )
8491 return []
8592
86- for importer , pluginName , ispkg in pkgutil .iter_modules (package .__path__ ):
87- pluginModuleName = '.' + pluginName
93+ for _ , pluginName , _ in pkgutil .iter_modules (package .__path__ ):
94+ pluginModuleName = "." + pluginName
8895
8996 try :
9097 pluginMod = importlib .import_module (pluginModuleName , package = package .__name__ )
91- plugins = [plugin for name , plugin in inspect .getmembers (pluginMod , inspect .isclass )
92- if plugin .__module__ == f' { package .__name__ } .{ pluginName } '
98+ plugins = [plugin for _ , plugin in inspect .getmembers (pluginMod , inspect .isclass )
99+ if plugin .__module__ == f" { package .__name__ } .{ pluginName } "
93100 and issubclass (plugin , classType )]
101+
94102 if not plugins :
95- logging .warning (f"No class defined in plugin: { pluginModuleName } " )
103+ # Only packages/folders have __path__, single module/file do not have it.
104+ isPackage = hasattr (pluginMod , "__path__" )
105+ # Sub-folders/Packages should not raise a warning
106+ if not isPackage :
107+ logging .warning (f"No class defined in plugin: { package .__name__ } .{ pluginName } ('{ pluginMod .__file__ } ')" )
96108
97- importPlugin = True
98109 for p in plugins :
99- if classType == desc .Node :
100- nodeErrors = validateNodeDesc (p )
101- if nodeErrors :
102- errors .append (" * {}: The following parameters do not have valid default values/ranges: {}"
103- .format (pluginName , ", " .join (nodeErrors )))
104- importPlugin = False
105- break
106110 p .packageName = packageName
107111 p .packageVersion = packageVersion
108112 p .packagePath = packagePath
109- if importPlugin :
110- classes .extend (plugins )
113+ if classType == desc .BaseNode :
114+ nodePlugin = NodePlugin (p )
115+ if nodePlugin .errors :
116+ errors .append (" * {}: The following parameters do not have valid " \
117+ "default values/ranges: {}" .format (pluginName , ", " .join (nodePlugin .errors )))
118+ classes .append (nodePlugin )
119+ else :
120+ classes .append (p )
111121 except Exception as e :
112122 tb = traceback .extract_tb (e .__traceback__ )
113123 last_call = tb [- 1 ]
@@ -124,41 +134,42 @@ def loadClasses(folder, packageName, classType):
124134 logging .warning (' The following "{package}" plugins could not be loaded:\n '
125135 '{errorMsg}\n '
126136 .format (package = packageName , errorMsg = '\n ' .join (errors )))
127- return classes
128137
138+ return classes
129139
130- def validateNodeDesc ( nodeDesc ) :
140+ def loadClassesNodes ( folder : str , packageName : str ) -> list [ NodePlugin ] :
131141 """
132- Check that the node has a valid description before being loaded. For the description
133- to be valid, the default value of every parameter needs to correspond to the type
134- of the parameter.
135- An empty returned list means that every parameter is valid, and so is the node's description.
136- If it is not valid, the returned list contains the names of the invalid parameters. In case
137- of nested parameters (parameters in groups or lists, for example), the name of the parameter
138- follows the name of the parent attributes. For example, if the attribute "x", contained in group
139- "group", is invalid, then it will be added to the list as "group:x".
142+ Return the list of all the NodePlugins that were created following the search of the
143+ Python module named "packageName" located in the folder "folder".
144+ A NodePlugin is created when a file within "packageName" that contains a class inheriting
145+ desc.BaseNode is found.
140146
141147 Args:
142- nodeDesc (desc.Node): description of the node
148+ folder: the folder to load the module from.
149+ packageName: the name of the module to look for nodes in.
143150
144151 Returns:
145- errors (list): the list of invalid parameters if there are any, empty list otherwise
152+ list[NodePlugin]: a list of all the NodePlugins that were created based on the
153+ module's search. If none has been created, an empty list is returned.
146154 """
147- errors = []
155+ return loadClasses ( folder , packageName , desc . BaseNode )
148156
149- for param in nodeDesc .inputs :
150- err = param .checkValueTypes ()
151- if err :
152- errors .append (err )
157+ def loadClassesSubmitters (folder : str , packageName : str ) -> list [BaseSubmitter ]:
158+ """
159+ Return the list of all the submitters that were found during the search of the
160+ Python module named "packageName" that located in the folder "folder".
161+ A submitter is found if a file within "packageName" contains a class inheriting
162+ from BaseSubmitter.
153163
154- for param in nodeDesc .outputs :
155- if param .value is None :
156- continue
157- err = param .checkValueTypes ()
158- if err :
159- errors .append (err )
164+ Args:
165+ folder: the folder to load the module from.
166+ packageName: the name of the module to look for nodes in.
160167
161- return errors
168+ Returns:
169+ list[BaseSubmitter]: a list of all the submitters that were found during the
170+ module's search
171+ """
172+ return loadClasses (folder , packageName , BaseSubmitter )
162173
163174
164175class Version :
@@ -250,7 +261,8 @@ def toComponents(versionName):
250261 status = ''
251262 # If there is a status, it is placed after a "-"
252263 splitComponents = versionName .split ("-" , maxsplit = 1 )
253- if (len (splitComponents ) > 1 ): # If there is no status, splitComponents is equal to [versionName]
264+ # If there is no status, splitComponents is equal to [versionName]
265+ if len (splitComponents ) > 1 :
254266 status = splitComponents [- 1 ]
255267 return tuple ([int (v ) for v in splitComponents [0 ].split ("." )]), status
256268
@@ -279,7 +291,7 @@ def micro(self):
279291 return self .components [2 ]
280292
281293
282- def moduleVersion (moduleName , default = None ):
294+ def moduleVersion (moduleName : str , default = None ):
283295 """ Return the version of a module indicated with '__version__' keyword.
284296
285297 Args:
@@ -292,7 +304,7 @@ def moduleVersion(moduleName, default=None):
292304 return getattr (sys .modules [moduleName ], "__version__" , default )
293305
294306
295- def nodeVersion (nodeDesc , default = None ):
307+ def nodeVersion (nodeDesc : desc . Node , default = None ):
296308 """ Return node type version for the given node description class.
297309
298310 Args:
@@ -305,38 +317,28 @@ def nodeVersion(nodeDesc, default=None):
305317 return moduleVersion (nodeDesc .__module__ , default )
306318
307319
308- def registerNodeType (nodeType ):
309- """ Register a Node Type based on a Node Description class.
310-
311- After registration, nodes of this type can be instantiated in a Graph.
312- """
313- if nodeType .__name__ in nodesDesc :
314- logging .error (f"Node Desc { nodeType .__name__ } is already registered." )
315- nodesDesc [nodeType .__name__ ] = nodeType
316-
317-
318- def unregisterNodeType (nodeType ):
319- """ Remove 'nodeType' from the list of register node types. """
320- assert nodeType .__name__ in nodesDesc
321- del nodesDesc [nodeType .__name__ ]
322-
323-
324- def loadNodes (folder , packageName ):
320+ def loadNodes (folder , packageName ) -> list [NodePlugin ]:
325321 if not os .path .isdir (folder ):
326322 logging .error (f"Node folder '{ folder } ' does not exist." )
327- return
323+ return []
328324
329- return loadClasses (folder , packageName , desc .BaseNode )
325+ nodes = loadClassesNodes (folder , packageName )
326+ return nodes
330327
331328
332- def loadAllNodes (folder ):
333- for importer , package , ispkg in pkgutil .walk_packages ([folder ]):
329+ def loadAllNodes (folder ) -> list [Plugin ]:
330+ plugins = []
331+ for _ , package , ispkg in pkgutil .iter_modules ([folder ]):
334332 if ispkg :
335- nodeTypes = loadNodes (folder , package )
336- for nodeType in nodeTypes :
337- registerNodeType (nodeType )
338- nodesStr = ', ' .join ([nodeType .__name__ for nodeType in nodeTypes ])
339- logging .debug (f'Nodes loaded [{ package } ]: { nodesStr } ' )
333+ plugin = Plugin (package , folder )
334+ nodePlugins = loadNodes (folder , package )
335+ if nodePlugins :
336+ for node in nodePlugins :
337+ plugin .addNodePlugin (node )
338+ nodesStr = ', ' .join ([node .nodeDescriptor .__name__ for node in nodePlugins ])
339+ logging .debug (f'Nodes loaded [{ package } ]: { nodesStr } ' )
340+ plugins .append (plugin )
341+ return plugins
340342
341343
342344def loadPluginFolder (folder ):
@@ -349,26 +351,29 @@ def loadPluginFolder(folder):
349351 logging .info (f"Plugin folder '{ folder } ' does not contain a 'meshroom' folder." )
350352 return
351353
352- binFolders = [Path (folder , 'bin' )]
353- libFolders = [Path (folder , 'lib' ), Path (folder , 'lib64' )]
354- pythonPathFolders = [Path (folder )] + binFolders
354+ processEnv = ProcessEnv (folder )
355+
356+ plugins = loadAllNodes (folder = mrFolder )
357+ if plugins :
358+ for plugin in plugins :
359+ pluginManager .addPlugin (plugin )
360+ pipelineTemplates .update (plugin .templates )
355361
356- loadAllNodes (folder = mrFolder )
357- loadPipelineTemplates (folder = mrFolder )
362+ return plugins
358363
359364
360365def loadPluginsFolder (folder ):
361366 if not os .path .isdir (folder ):
362367 logging .debug (f"PluginSet folder '{ folder } ' does not exist." )
363368 return
364-
369+
365370 for file in os .listdir (folder ):
366371 if os .path .isdir (file ):
367372 subFolder = os .path .join (folder , file )
368373 loadPluginFolder (subFolder )
369374
370375
371- def registerSubmitter (s ):
376+ def registerSubmitter (s : BaseSubmitter ):
372377 if s .name in submitters :
373378 logging .error (f"Submitter { s .name } is already registered." )
374379 submitters [s .name ] = s
@@ -379,30 +384,31 @@ def loadSubmitters(folder, packageName):
379384 logging .error (f"Submitters folder '{ folder } ' does not exist." )
380385 return
381386
382- return loadClasses (folder , packageName , BaseSubmitter )
383-
387+ return loadClassesSubmitters (folder , packageName )
384388
385- def loadPipelineTemplates (folder ):
389+ def loadPipelineTemplates (folder : str ):
386390 if not os .path .isdir (folder ):
387391 logging .error (f"Pipeline templates folder '{ folder } ' does not exist." )
388392 return
389393 for file in os .listdir (folder ):
390394 if file .endswith (".mg" ) and file not in pipelineTemplates :
391395 pipelineTemplates [os .path .splitext (file )[0 ]] = os .path .join (folder , file )
392396
393-
394397def initNodes ():
395398 additionalNodesPath = EnvVar .getList (EnvVar .MESHROOM_NODES_PATH )
396- nodesFolders = [os .path .join (meshroomFolder , ' nodes' )] + additionalNodesPath
399+ nodesFolders = [os .path .join (meshroomFolder , " nodes" )] + additionalNodesPath
397400 for f in nodesFolders :
398- loadAllNodes (folder = f )
401+ plugins = loadAllNodes (folder = f )
402+ if plugins :
403+ for plugin in plugins :
404+ pluginManager .addPlugin (plugin )
399405
400406
401407def initSubmitters ():
402408 additionalPaths = EnvVar .getList (EnvVar .MESHROOM_SUBMITTERS_PATH )
403409 allSubmittersFolders = [meshroomFolder ] + additionalPaths
404410 for folder in allSubmittersFolders :
405- subs = loadSubmitters (folder , ' submitters' )
411+ subs = loadSubmitters (folder , " submitters" )
406412 for sub in subs :
407413 registerSubmitter (sub ())
408414
@@ -411,13 +417,15 @@ def initPipelines():
411417 # Load pipeline templates: check in the default folder and any folder the user might have
412418 # added to the environment variable
413419 additionalPipelinesPath = EnvVar .getList (EnvVar .MESHROOM_PIPELINE_TEMPLATES_PATH )
414- pipelineTemplatesFolders = [os .path .join (meshroomFolder , ' pipelines' )] + additionalPipelinesPath
420+ pipelineTemplatesFolders = [os .path .join (meshroomFolder , " pipelines" )] + additionalPipelinesPath
415421 for f in pipelineTemplatesFolders :
416422 loadPipelineTemplates (f )
423+ for plugin in pluginManager .getPlugins ().values ():
424+ pipelineTemplates .update (plugin .templates )
417425
418426
419427def initPlugins ():
420428 additionalpluginsPath = EnvVar .getList (EnvVar .MESHROOM_PLUGINS_PATH )
421- nodesFolders = [os .path .join (meshroomFolder , ' plugins' )] + additionalpluginsPath
429+ nodesFolders = [os .path .join (meshroomFolder , " plugins" )] + additionalpluginsPath
422430 for f in nodesFolders :
423431 loadPluginFolder (folder = f )
0 commit comments