Source code for robot_designer_plugin.core.pluginmanager

# #####
# This file is part of the RobotDesigner of the Neurorobotics subproject (SP10)
# in the Human Brain Project (HBP).
# It has been forked from the RobotEditor (https://gitlab.com/h2t/roboteditor)
# developed at the Karlsruhe Institute of Technology in the
# High Performance Humanoid Technologies Laboratory (H2T).
# #####

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# #####
#
# Copyright (c) 2016, FZI Forschungszentrum Informatik
#
# Changes:
#
#   2016-01-15: Stefan Ulbrich (FZI), Major refactoring. Integrated into complex plugin framework.
#
# ######

"""
This sub module contains the :class:`PluginManager` that handles automatic registration of classes.
"""
import inspect
from enum import Enum
import os

import bpy
import bpy.utils

from .config import EXCEPTION_MESSAGE, resource_path
from .logfile import core_logger, log_callstack
from .operators import RDOperator
from .gui import CollapsibleBase


[docs]class PluginManager(object): """ This class handles the automatic registration of classes to Blender. """ _classes_to_register = [] _registered_classes = [] _property_groups_to_register = [] _registered_properties = [] _file_plugins = [] _bools_to_register = [] _registered_bools = [] PluginTypes = Enum('PluginType', 'FILE GEOMETRY SEGMENTS MODEL') _bl_icons_dict = None _icons_to_register = [] _property_fields = {}
[docs] @staticmethod def register_property_group(base=None): propertyTypes = (bpy.props.EnumProperty, bpy.props.StringProperty, bpy.props.BoolProperty, bpy.props.StringProperty, bpy.props.PointerProperty) def decorator(cls): if issubclass(cls, bpy.types.PropertyGroup): # RD_logger.info("Dependencies: {}".format(base)) PluginManager._property_groups_to_register.append((cls, base)) return cls return decorator
[docs] @staticmethod def register_class(*args): """ :term:`Class decorator<class decorator>` for :class:`.operators.RDOperator`, :class:`bpy.types.Menu`, :class:`bpy.types.PropertyGroup` (and more ``bpy`` classes) and :class:`.gui.CollapsibleBox`. An example of how to use this decorator is given in :class:`.operators.RDOperator`. :param args: For now, no arguments should be passed to the decorator. """ def decorator(cls): if issubclass(cls, (RDOperator, bpy.types.Menu, bpy.types.Panel, bpy.types.Panel)): PluginManager._classes_to_register.append((cls, dependencies)) elif issubclass(cls, CollapsibleBase): PluginManager._bools_to_register.append(cls.property_name) else: core_logger.error("Could not register %s, subclass of: {}\nDependencies: {}\n{}".format(cls, cls.mro(), dependencies, args)) raise TypeError("Wrong with decorator") # print(cls) if not cls.__doc__: cls.__doc__ = "Missing documentation.\n\n" cls.__doc__ += "\n **Automatically registered** to " \ ":class:`robot_designer_plugin.core.pluginmanager.Pluginmanager`\n" return cls if len(args) == 1 and inspect.isclass(args[0]): # No arguments dependencies = [] return decorator(args[0]) else: # Arguments given to decorator dependencies = args return decorator
[docs] @classmethod def register_property_groups(cls, property_group, btype): """ :term:`Properties<property>` have to be assigned to blender types. This function collects all properties and assigns them when :meth:`register` is called and unloads them when :meth:`unregister` is called. :param property_group: A class derived from :class:`bpy.types.PropertyGroup`. :param btype: Any type defined in :mod:`bpy.types`. :return: """
# cls._property_groups_to_register.append((property_group, btype)) # props = inspect.getmembers(property_group, lambda x: isinstance(x, tuple) and len(x) == 2) # print(props) # cls._property_fields[property_group]=[] # for name, prop in props: # cls._property_fields[property_group].append((name, prop)) # # for nested in inspect.getmembers(property_group, lambda x: isinstance(x, bpy.types.PropertyGroup)): # cls._property_fields[property_group].append( # # )
[docs] @classmethod def register_plugin(cls, label, operators, draw_function=None, type_=PluginTypes.FILE): """ .. warning:: The plugin system is still under development. """ cls._file_plugins.append((label, operators, draw_function, type_))
@classmethod def get_property(cls, property): """ .. warning:: Better handling of properties is still under development. """ return property[1]['attr']
[docs] @classmethod def getFilePlugins(cls, type_=PluginTypes.FILE): """ .. warning:: The plugin system is still under development. """ return cls._file_plugins
[docs] @classmethod def register_collapsible(cls, property_name): """ Called when a class of type :class:`.gui.CollapsibleBox` is created. """ cls._bools_to_register.append(property_name)
[docs] @classmethod def load_icon(cls, id: str, filename: str): ''' :param id: ID of the label :param filename: Relative to :data:`.config/resource_path` :return: None ''' file_path = os.path.join(resource_path, filename) if not os.path.exists(file_path): core_logger.debug("File does not exist: {}".format(file_path)) else: cls._icons_to_register.append((id, file_path, 'IMAGE'))
[docs] @classmethod def get_icon(cls, id: str): ''' :param id: :return: ''' if cls._bl_icons_dict and id in cls._bl_icons_dict: return cls._bl_icons_dict[id].icon_id else: return 0
[docs] @classmethod def clear(cls): """ When a plugin is required to be reloadable (which it should be during development) each module is imported twice. Therefore, this method is called before the modules are reloaded. """ cls._classes_to_register.clear() cls._property_groups_to_register.clear() cls._bools_to_register.clear() cls._icons_to_register.clear()
[docs] @classmethod def get_property(cls, obj, prop): ''' :param obj: :param prop: :return: ''' args, varargs, keywords, locals = inspect.getargvalues(inspect.currentframe()) print(args, varargs, keywords, locals)
[docs] @classmethod def register(cls): """ Called from the top-level :func:`robot_designer_plugin.register` function in the ``__init__.py`` of the plugin. Registers all data collected during import. """ report = ['\n'] try: for class_, dependencies in cls._classes_to_register: bpy.utils.register_class(class_) report.append('\t+ class {0:35} in {1:40}'.format(class_.__name__, "/".join(class_.__module__.split('.')[1:]))) cls._registered_classes.append(class_) core_logger.info('Done') core_logger.debug("Properties: {}".format(cls._property_groups_to_register)) for prop, extends in cls._property_groups_to_register: report.append("\t+ property {0:33} {1:8} in {2:40}".format(prop.__name__, "(%s)" % extends.__name__ if extends else '', "/".join(prop.__module__.split('.')[1:]))) bpy.utils.register_class(prop) if extends in (bpy.types.Object, bpy.types.Scene, bpy.types.Bone): setattr(extends, 'RobotDesigner', bpy.props.PointerProperty(type=prop)) cls._registered_properties.append((prop, extends)) for i in cls._property_fields.items(): print(i) for prop in cls._bools_to_register: setattr(bpy.types.Scene, prop, bpy.props.BoolProperty()) cls._registered_bools.append(prop) if cls._icons_to_register: cls._bl_icons_dict = bpy.utils.previews.new() for icon in cls._icons_to_register: report.append("\t+ icon {0:36} from {1:40}".format(icon[0], icon[1])) cls._bl_icons_dict.load(*icon) core_logger.info("\n".join(report)) except Exception as e: report.append("Error occured") core_logger.info("\n{}".format(report)) core_logger.error(EXCEPTION_MESSAGE, type(e).__name__, e, log_callstack(), log_callstack(back_trace=True)) cls.clear()
[docs] @classmethod def unregister(cls): """ Called from the top-level :func:`robot_designer_plugin.unregister` function in the ``__init__.py`` of the plugin. Removes all data collected during import. """ report = ['\n'] try: for class_ in cls._registered_classes: bpy.utils.unregister_class(class_) report.append("\t- class {0:35} in {1:40}".format(class_.__name__, "/".join(class_.__module__.split('.')[1:]))) for prop, extends in cls._registered_properties: bpy.utils.unregister_class(prop) if extends in (bpy.types.Object, bpy.types.Scene, bpy.types.Bone) and \ hasattr(extends, "RobotDesigner"): delattr(extends, "RobotDesigner") for prop in cls._registered_bools: delattr(bpy.types.Scene, prop) core_logger.info("\n".join(report)) if cls._bl_icons_dict: bpy.utils.previews.remove(cls._bl_icons_dict) except Exception as e: report.append("Error occured during clean up. You should restart blender!") core_logger.info("\n".join(report)) core_logger.error(EXCEPTION_MESSAGE, type(e).__name__, e, log_callstack(), log_callstack(back_trace=True)) cls._registered_classes.clear() cls._property_groups_to_register.clear() cls._registered_bools.clear()