class OsimImporter(object):
    """
    Class representing the .osim import
    """

    def __init__(self, file_path, musclepath):
        # initialize logger and operator
        operator = RDOperator
        self.logger = operator.logger
        self.operator = operator
        self.controllers = None

        # create dom object from .osim file
        base_dir = os.path.dirname(file_path)
        self.logger.debug(base_dir)
        self.logger.debug(musclepath)
        export_logger.debug("mpath: {}".format(musclepath))
        muscles_osim = open(
            base_dir + "/" + "/".join(musclepath.split("/", 3)[3:])[:-2]
        ).read()
        self.muscles = osim_dom.CreateFromDocument(muscles_osim)

    def import_muscles(self, muscle, type):
        """
        import a single muscle from the osim file
        :param muscle: .osim pyxb muscle instance
        :return: type: string for muscle type
        """
        CreateNewMuscle.run(muscle.name)
        RDmuscle = bpy.data.objects[muscle.name]

        RDmuscle.RobotDesigner.muscles.muscleType = type

        if type in ["THELEN", "MILLARD_EQUIL", "MILLARD_ACCEL", "RIGID_TENDON"]:

            RDmuscle.RobotDesigner.muscles.length = muscle.optimal_fiber_length[0] / 0.9
            RDmuscle.RobotDesigner.muscles.max_isometric_force = (
                muscle.max_isometric_force[0]
            )

        global_properties.active_muscle.set(bpy.context.scene, muscle.name)

        self.import_pathpoints(muscle, RDmuscle)
        self.connect_wrapping_objects(muscle, RDmuscle)

    def connect_wrapping_objects(self, muscle, RDmuscle):
        """
        Create dependencies between muscle and corresponding wrapping objects
        :param muscle: .osim pyxb muscle instance
        :param RDmuscle: muscle object
        :return:
        """

        # iterate through all connected wrapping objects to the pyxb muscle instance
        w = 0
        while True:
            try:
                # add muscle to muscle list of all connected wrapping objects
                path = muscle.GeometryPath.PathWrapSet.objects.PathWrap[w]
                wrapping_object = bpy.context.scene.objects[path.wrap_object]
                wrapping_object.RobotDesigner.wrap.muscleNames.add()
                nr = len(wrapping_object.RobotDesigner.wrap.muscleNames)
                wrapping_object.RobotDesigner.wrap.muscleNames[
                    nr - 1
                ].name = RDmuscle.name

                # add wrapping object to wrapping objects list of muscle
                wrapList = RDmuscle.RobotDesigner.muscles.connectedWraps
                wrapList.add()
                nrw = len(wrapList)
                wrapList[nrw - 1].wrappingName = wrapping_object.name
                w += 1
            except:
                break

    def import_pathpoints(self, muscle, RDmuscle):
        """
        Import muscle pathpoints from the osim file
        :param muscle: .osim pyxb muscle instance
        :return: RDmuscle: Robot Designer muscle instance
        """
        p = 0
        while True:
            try:
                # current pathpoint
                pathpoint = muscle.GeometryPath.PathPointSet.objects.PathPoint[p]

                # get pathpoint parent world pose
                model = bpy.data.objects[
                    global_properties.model_name.get(bpy.context.scene)
                ]
                pose_bone = model.pose.bones[pathpoint.body]
                segment_world = model.matrix_world @ pose_bone.matrix

                # calculate pathpoint world pose
                location_local = [float(x) for x in pathpoint.location.split()]
                location_global = segment_world @ Matrix.Translation(
                    (location_local[0], location_local[1], location_local[2], 1)
                )

                # create new pathpoint and set parameters of RD pathpoint object
                CreateNewPathpoint.run()
                location_global = location_global.to_translation()
                RDmuscle.data.splines[0].points[p].co = (
                    location_global[0],
                    location_global[1],
                    location_global[2],
                    1,
                )

                #  hook pathpoints to segments
                RDmuscle.RobotDesigner.muscles.pathPoints[p].coordFrame = pathpoint.body
                bpy.ops.RobotDesigner.select_segment_muscle(
                    segment_name=pathpoint.body, pathpoint_nr=p + 1
                )

                p += 1

            except:
                break

    def import_wrapping_sphere(self, body, wrapping):
        """
        Create wrapping sphere from osim file
        :param body: parent segment
        :param wrapping: .osim pyxb wrapping object instance
        :return:
        """
        radius = wrapping.radius
        bpy.ops.mesh.primitive_uv_sphere_add(
            radius=1.0, calc_uvs=True, enter_editmode=False, location=(0, 0, 0)
        )
        sphere = bpy.context.active_object
        sphere.name = wrapping.name

        lmat = bpy.data.materials.new(sphere.name)
        lmat.diffuse_color = (0.0, 0.135, 0.0, 1.0)
        # lmat.use_shadeless = True
        sphere.data.materials.append(lmat)

        sphere.RobotDesigner.tag = "WRAPPING"
        sphere.RobotDesigner.wrap.WrappingType = "WRAPPING_SPHERE"

        model = bpy.data.objects[global_properties.model_name.get(bpy.context.scene)]
        pose_bone = model.pose.bones[body.name]
        segment_world = model.matrix_world @ pose_bone.matrix

        model_posexyz = string_to_list(wrapping.translation[:])[0:3]
        model_poserpy = [0, 0, 0]
        trafo = (
            Matrix.Translation(Vector(model_posexyz))
            @ Euler(model_poserpy, "XYZ").to_matrix().to_4x4()
        )

        bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
        bpy.context.active_object.matrix_world = (
            segment_world @ trafo @ bpy.context.active_object.matrix_world
        )

        assigned_name = bpy.context.active_object.name

        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
        SelectModel.run(model_name=model.name)
        SelectSegment.run(segment_name=body.name)
        SelectGeometry.run(geometry_name=assigned_name)

        bpy.data.objects[sphere.name].RobotDesigner.wrap_scaling.scale_all = radius
        # Model has to be selected and active in order to update scale
        # Geometry has to be selected, else the update function itself will take a different object
        bpy.data.objects[sphere.name].RobotDesigner.wrap_scaling.scale_all_update(
            bpy.context
        )

        AttachWrappingObject.run()

    def import_wrapping_cylinder(self, body, wrapping):
        """
        Create wrapping cylinder from osim file
        :param body: parent segment
        :param wrapping: .osim pyxb wrapping object instance
        :return:
        """
        radius = wrapping.radius
        depth = wrapping.length
        bpy.ops.mesh.primitive_cylinder_add(
            radius=1.0, depth=1.0, enter_editmode=False, location=(0, 0, 0)
        )
        cylinder = bpy.context.active_object
        cylinder.name = wrapping.name

        lmat = bpy.data.materials.new(cylinder.name)
        lmat.diffuse_color = (0.0, 0.135, 0.0, 1.0)
        # lmat.use_shadeless = True
        cylinder.data.materials.append(lmat)

        cylinder.RobotDesigner.tag = "WRAPPING"
        cylinder.RobotDesigner.wrap.WrappingType = "WRAPPING_CYLINDER"

        model = bpy.data.objects[global_properties.model_name.get(bpy.context.scene)]
        pose_bone = model.pose.bones[body.name]
        segment_world = model.matrix_world @ pose_bone.matrix

        model_posexyz = string_to_list(wrapping.translation[:])
        model_poserpy = string_to_list(wrapping.xyz_body_rotation[:])
        trafo = (
            Matrix.Translation(Vector(model_posexyz))
            @ Euler(model_poserpy, "XYZ").to_matrix().to_4x4()
        )

        bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
        bpy.context.active_object.matrix_world = (
            segment_world @ trafo @ bpy.context.active_object.matrix_world
        )

        assigned_name = bpy.context.active_object.name

        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
        SelectModel.run(model_name=model.name)
        SelectSegment.run(segment_name=body.name)
        SelectGeometry.run(geometry_name=assigned_name)

        bpy.data.objects[cylinder.name].RobotDesigner.wrap_scaling.scale_radius = radius
        bpy.data.objects[cylinder.name].RobotDesigner.wrap_scaling.scale_depth = depth
        # Model has to be selected and active in order to update scale
        # Geometry has to be selected, else the update function itself will take a different object
        bpy.data.objects[cylinder.name].RobotDesigner.wrap_scaling.scale_radius_update(
            bpy.context
        )
        bpy.data.objects[cylinder.name].RobotDesigner.wrap_scaling.scale_depth_update(
            bpy.context
        )

        AttachWrappingObject.run()

    def import_osim(self):
        """
        Imports all listed muscles and wrapping objects in .osim file
        """
        # import wrapping objects
        b = 0
        while True:
            try:
                body = self.muscles.Model.BodySet[0].objects.Body[b]
                export_logger.info("\nimporting for {}".format(body.name))
                s = 0
                while True:
                    try:
                        wrapping_sphere = body.WrapObjectSet[0].objects.WrapSphere[s]
                        export_logger.info(
                            "importing wrapping sphere: {}".format(wrapping_sphere.name)
                        )
                        self.import_wrapping_sphere(body, wrapping_sphere)
                        s += 1
                    except:
                        break
                c = 0
                while True:
                    try:
                        wrapping_cylinder = body.WrapObjectSet[0].objects.WrapCylinder[
                            c
                        ]
                        export_logger.info(
                            "importing wrapping cylinder: {}".format(wrapping_cylinder.name)
                        )
                        self.import_wrapping_cylinder(body, wrapping_cylinder)
                        c += 1
                    except:
                        break
                b += 1
            except:
                break

        # import Thelen2003 Muscles
        m = 0
        while True:
            try:
                muscle = self.muscles.Model.ForceSet.objects.Thelen2003Muscle[m]
                type = "THELEN"
                self.import_muscles(muscle, type)
                m += 1
                export_logger.info("import thelen")
            except:
                break

        # import Millard2012 Equilibrium Muscles
        m = 0
        while True:
            try:
                muscle = (
                    self.muscles.Model.ForceSet.objects.Millard2012EquilibriumMuscle[m]
                )
                type = "MILLARD_EQUIL"
                self.import_muscles(muscle, type)
                m += 1
            except:
                break

        # import Millard2012 Acceleration Muscles
        m = 0
        while True:
            try:
                muscle = (
                    self.muscles.Model.ForceSet.objects.Millard2012AccelerationMuscle[m]
                )
                type = "MILLARD_ACCEL"
                self.import_muscles(muscle, type)
                m += 1
            except:
                break

        # import Rigid Tendon Muscles
        m = 0
        while True:
            try:
                muscle = self.muscles.Model.ForceSet.objects.RigidTendonMuscle[m]
                type = "RIGID_TENDON"
                self.import_muscles(muscle, type)
                m += 1
            except:
                break

        # import Myorobotics Muscles
        m = 0
        while True:
            try:
                muscle = self.muscles.Model.ForceSet.objects.MyoroboticsMuscle[m]
                type = "MYOROBOTICS"
                self.import_muscles(muscle, type)
                m += 1
            except:
                break PLUGIN_PREFIX = "robotdesigner"
OPERATOR_PREFIX = PLUGIN_PREFIX + '.'
MENU_PREFIX = "ROBOTDESIGNER_MT_"
BACKTRACE_FILTER_FUNC = ('func_wrapper', 'op_logger', '__call__')
BACKTRACE_FILTER_HIDE_CODE = ('run', 'runNestedOperator')
BACKTRACE_MESSAGE_STACK_CODE = "\t\t{0:25}\t\t({1}:{2})\n\t\t\t{3}\n"
BACKTRACE_MESSAGE_STACK = "\t\t{0:25}\t\t({1}:{2})\n"
BACKTRACE_MESSAGE_CALLSTACK = "\n\tNesting Level %d:\n\n\tCallstack:\n"
BACKTRACE_MESSAGE = "\n\tBacktrace:\n"
EXCEPTION_MESSAGE = "\tException:\t%s\n\tMessages:\n\t\t%s\n%s\n%s" 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 = {}

    @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



    @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

    @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(
        #
        #     )

    @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']

    @classmethod
    def getFilePlugins(cls, type_=PluginTypes.FILE):
        """
        .. warning::

            The plugin system is still under development.
        """
        return cls._file_plugins

    @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)

    @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'))


    @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


    @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()


    @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)


    @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()


    @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() class Condition(object):
    """
    Base Class for conditions.

    Classes should implement a ``check()`` method which returns a tuple of (bool,str) stating whether
    the condition's are met and an (potentially) empty error message.

    Using the :meth:`.operators.RDOperator.Preconditions` decorator, the conditions that should be met
    when executing an operator (i.e., a subclass of :class:`.operators.RDOperator`) are added to the classes'
    list of preconditions and :meth:`check_conditions` is called when Blender executes the
    :meth:`.operators.RDOperator.poll` method.

    .. note::

        Instances of this type should never be created.

    For examples see :class:`robot_designer_plugin.operators.helpers.ModelSelected`.
    """

    @staticmethod
    def check_conditions(*conditions):
        """
        Checks whether the arguments are met.

        :param conditions: A an arbitrary number of conditions
        :type conditions: :class:`Condition` sub classes.
        :return: Tuple(bool, str), True and empty string if all conditions are met. False and sting with message of
        unmet conditions.
        """
        try:
            check = [i.check() for i in conditions]
            return all([i[0] for i in check]), "\n".join([i[1] for i in check if not i[0]])

        except Exception as e:
            logger.error("Exception in check precondition!\n {} \nConditions: {}  {} {}".format(EXCEPTION_MESSAGE,
                         type(e).__name__, e, log_callstack(), log_callstack(True), conditions))
            return False, "Exception raised"

    @staticmethod
    def call():
        '''
        Needs to be overridden in sub classes

        :raises: NotImplementedError
        '''
        raise NotImplementedError("Must be overridden in subclasses") class RDOperator(bpy.types.Operator):
    """
    Base class for the :term:`operators<operator>` in the RobotDesigner.

    :term:`Blender operators<operator>` are defined as :term:`class objects` derived from :class:`bpy.types.Operator`.
    A plugin must *register* these classes by calling :func:`bpy.utils.register_class` which causes blender to
    create a callable function *out of a method* (:meth:`bpy.types.Operator.execute`) that accepts
    :term:`keyword arguments <keyword argument>` that are bound to the class' attributes -- if they are
    subclasses
    of :class:`bpy.types.Property`. Note that the static method has not the same arguments and operates on its
    attributes.
    Finally, the operator is addressed to by its ID. For example:

    .. code-block:: python

        class NewOp(bpy.types.Operator):
            bl_label = "Description" # Obligatory
            bl_idname = "plugin_name.newop" # obligatory

            test = StringProperty() # A property. It's value is passed when calling the operator.

            def execute(self, context):
                self.report({INFO}, self.text)
                return {'FINISHED'}

        # ...

        bpy.utils.register(NewOp)

        # ...

        bpy.ops.plugin_name.newop(test="Hello World)


    This example show an operator that opens a window containing the message *"Hello World"*. While this is feasible
    for smaller plugins, what happens, if you have a lot of operators written, and you want to have code completion.
    What happens if you want to refactor the names of of the identifier or class attributes. While for the latter there
    is no simple solution available, the :mod:`robot_designer_plugin.core` provides this wrapper class with
    an additional ``run(**kwargs)`` method (that can---and should---be overridden to provide keyword completion).
    Furthermore, it provides decorators for automatically setting preconditions (that define the
    :meth:`bpy.types.Operator.poll` method) for automatic condition checking, logging functionality and post condition
    checking. Together with the :meth:`.pluginmanager.PluginManager.register` method, this makes developing faster (
    code completion,
    code navigation, less verbosity, ...) and safer.

    An examplary :term:`operator` looks like this:

    .. literalinclude:: ../../../resources/templates/operator.py
        :emphasize-lines: 7-11,16-18, 31-33, 60-62
        :lines: 50-111
        :linenos:

    A template is located in ``<RobotDesignerDirectory>/resources/templates/operator.py`` (where you should insert
    the path to the repository) that can be used to quickly write new operators.
    """

    _pre_conditions = {}
    """Dictionary set by the :meth:`PreConditions` decorator. Stores the :class:`.conditions.Condition` that are
    checked for each :class:`RDOperator` -based operator"""

    logger = logger
    """For convenience, every the operators share a logging instance."""

    @classmethod
    def poll(cls, context):
        """
        The :meth:`bpy.types.Operator.poll` class method is called every time, an operator is to be executed, but
        also when it is placed in a guy (it becomes greyed out). This method is defined in this base class and
        the checks are defined by a list of :class:`.conditions.Condition` derived classes that are given by
        the :meth:`Preconditions` decorator. Derived classes do not need to override this method and should
        call the parent's method otherwise.

        :param context: The actual Blender :term:`context`.
        :type context: :class:`bpy.types.Context`
        :return:
        """
        if cls in cls._pre_conditions:
            check, messages = Condition.check_conditions(*cls._pre_conditions[cls])
            if not check:
                cls.logger.debug("Unmet preconditions: \n{}".format(messages))
            return check
        return True

    @staticmethod
    def pass_keywords():
        """
        Helper function that extracts the arguments of the callee (must be a (class) method) and returns them.

        Credits to `Kelly Yancey <http://kbyanc.blogspot.de/2007/07/python-aggregating-function-arguments.html>`_
        """

        args, _, _, locals = inspect.getargvalues(inspect.stack()[1][0])
        args.pop(0)
        kwargs = {i: j for i, j in locals.items() if i in args}
        return kwargs

    @classmethod
    def run(cls, **kwargs):
        """
        Enables to run an :ref:`operator` by calling its class object.
        Child classes *should* override this method with keywords. Then your :term:`IDE` will be able to assist you
        The :meth:`pass_keywords` function makes
        overriding very convenient (just copy the method's body):

        .. code: python

            class NewOp(RDOperator):
                model_name = StringProperty(name="Enter model name:")
                base_segment_name = StringProperty(name="Enter root segment name:", default="")

                @classmethod
                def run(cls, model_name, base_segment_name):
                    return super().run(**cls.pass_keywords())

        .. warning:

            :term: `refactoring<Refactoring>` class attributes (i.e., the keywords) requires you modify both the
            overridden run function *and* your attributes.

        In addition, this method also does extensive error and exception handling writing everything to the log file
        (see :mod:`.logfile`).

        :param kwargs: The keyword arguments passed to the :ref:`Operator` (**must** match the names of the class's
            attributes (only of type :class:`bpy.types.Property).
        :return: The return values of :meth:`bpy.types.Operator.execute`.
        """
        try:
            # cls.logger.debug('get registered operator')
            return get_registered_operator(cls)(**kwargs)


        except TypeError as e:
            bad_kwargs = []
            for kwarg in kwargs.keys():
                if kwarg not in dir(cls):
                    bad_kwargs.append(kwarg)

            cls.logger.error('Exception when running operator {} ({}):'
                             '\n\tkeywords:\t%s\n\tBad keywords:\t{}'
                             '\n\tException:\t%s\n\tMessages:\n\t\t{}'.format(
                             cls.bl_idname, cls.__name__,
                             kwargs,
                             ', '.join(bad_kwargs), type(e).__name__,
                             '\n\t\t'.join(e.__str__().split('\n'))))
            InfoBox.global_messages.append(e.__str__())
            raise e

            # todo check for keyword arguments without default value

        except RuntimeError as e:
            cls.logger.error('Exception when running operator {} ({}):'
                             '\n\tException:\t{}\n\t{}\n'.format(cls.bl_idname, cls.__name__,
                             type(e).__name__, '\n\t'.join(e.__str__().split('\n'))))
            check, messages = Condition.check_conditions(*cls._pre_conditions[cls])
            cls.logger.info("Conditions set for this operator: {}\n\t{}\n\t{}".format(cls._pre_conditions[cls], check,
                            messages))
            InfoBox.global_messages.append(e.__str__())
            raise e
        except Exception as e:
            cls.logger.error('THIS CODE SHOULD NEVER BE EXECUTED: {}\n{}\n'.format(e, log_callstack(back_trace=True)))
            InfoBox.global_messages.append(e.__str__())
            raise e

    @classmethod
    def place_button(cls, layout, text=None, infoBox=None, **kwargs):
        """
        This is a convenience method that let's the developer create a button executing the operator from the gui.
        The results of the :meth:`poll` method (i.e., checking it's conditions) can be optionally passed to an
        :class:`.gui.InfoBox` instance such that a report for a group of operators can be displayed (i.e., why
        it is not executable)

        :param layout: the gui element to place the button (see :meth:`bpy.types.Panel.draw`)
        :param text: Optionally override the text in the ``bl_label`` attribute.
        :param infoBox: Optional instance of :class:`.gui.InfoBox`
        :param kwargs: Additional keyword arguments to :meth:`bpy.types.UILayout.operator`)
        :return: The operator instance (this can be used to set one of the class's attributes)
        """
        if infoBox and cls in cls._pre_conditions:
            # if not isinstance(infoBox, InfoBox):
            #    raise TypeError("Argument: infobox must be of type %s (not %s)" % (InfoBox.__name__, type(infoBox)))

            check, messages = Condition.check_conditions(*cls._pre_conditions[cls])
            if not check:
                infoBox.add_message(messages)

        # Checkconditions
        # todo check KWargs
        if not text:
            text = cls.bl_label
        return layout.operator(cls.bl_idname, text=text, **kwargs)

    @staticmethod
    def OperatorLogger(func):
        """
        Decorator for the `bpy.types.Operator.execute` method (only for sub classes of :class:`RDOperator`).

        Performs logging and exception handling.
        """

        def op_logger(self, context):

            # `callargs['self']` parameter refers to the operator instance
            # This is done in order to avoid positional arguments.
            # That way, the decorator can be used as @OperationLogger instead of
            # @OperationLogger
            # callargs = inspect.getcallargs(func,*args)

            class_name = self.__class__.__name__
            id = self.__class__.bl_idname

            # Execute the Operator
            try:
                # self.logger.debug("Entering {}() from {}({}):\n{}".format(
                #                    func.__name__, id, class_name,
                #                    log_callstack()))

                result = func(self, context)
                # self.logger.debug("Leaving {}() {}({})".format(id, class_name, result))

                return result

            except Exception as e:
                InfoBox.global_messages.append(
                    "%s: %s (%s)" % (type(e).__name__, e.__str__(), log_callstack_last(back_trace=True)))
                message = "Operator %s (%s) threw an exception:%s\n\t%s" % (id, class_name,
                                                                            type(e).__name__, e)
                self.logger.error("Operator {} ({}) threw an exception:\n {} {} {} {}".format(EXCEPTION_MESSAGE,
                                  id, class_name, type(e).__name__, e, log_callstack(), log_callstack(back_trace=True)))
                if isinstance(self, RDOperator):
                    self.report({'ERROR'}, message)
                    return {'FINISHED'}
                else:
                    # Menu has no report and must return None
                    return

        return op_logger

    @staticmethod
    def Preconditions(*conditions):
        """
        :term:`Class decorator<class decorator>` for :term:`operators<operator>`.

        Registers the conditions (of type :class:`.conditions.Condition`) for its subclasses. They will be
        checked within the :meth:`poll` and :meth:`place_button` methods.

        :param conditions: a number of subclasses of :class:`.conditions.Condition`.
        """

        def decorator(cls: RDOperator):  # Todo check why this check fails some times
            if not issubclass(cls, RDOperator):
                raise TypeError("Preconditions: Can only decorate sub classes of RDOperator.i \n%s \n%s \n%s\n%s" % (
                    cls.__name__,
                    cls.mro(), type(cls), conditions))

            cls._pre_conditions[cls] = conditions

            cls.__doc__ += "\n    **Preconditions**:\n\n%s" % "".join(
                '    * :class:`%s.%s`\n' % (i.__module__, i.__name__) for i in conditions)

            # RDOperator.logger.debug("Decorating {}, \nArgs: {}\n {}, {}, {}".format(cls, args, issubclass(cls, RDOperator),
            #                        issubclass(cls, bpy.types.Operator), [RDOperator is i for i in cls.mro()]))
            # for i in cls.mro():
            #    print(i)
            return cls

        # RDOperator.logger.debug("Precondition:\nArgs: {}".format(args))
        if len(conditions) == 1 and issubclass(conditions[0], RDOperator):
            raise TypeError('Decorator Preconditions must be called with arguments of subclasses of Condition')
        else:
            if any([issubclass(i, RDOperator) for i in conditions]):
                raise TypeError('Decorator Preconditions must be called with arguments of subclasses of Condition '
                            '%s', conditions)

            return decorator


    @staticmethod
    def Postconditions(*conditions):
        """
        Method decorator for the :meth:`bpy.types.Operator.execute` method. Works only with :term:`operators<operator>`
        derived :class:`RDOperator`.

        Checks whether post conditions are met after calling the operator. Reports an error message to the log file
        (see :mod:`.logfile`)

        :param conditions: a number of subclasses of :class:`.conditions.Condition`.
        """

        def decorator(func):
            def func_wrapper(self, context):
                result = func(self, context)
                check, messages = Condition.check_conditions(*conditions)
                if not check:
                    self.report({'ERROR'}, messages)
                    logger.error('Postcondition not met: %s\\n%s\n%s', messages, log_callstack(), log_callstack(True))
                    InfoBox.global_messages.append(messages)
                return result

            doc = "\n    **Postconditions**:\n\n%s" % "".join(
                '    * :class:`%s.%s`\n' % (i.__module__, i.__name__) for i in conditions)
            func.__doc__ = func.__doc__ + doc if func.__doc__ else doc
            return func_wrapper

        return decorator class ModelSelected(Condition):
    @staticmethod
    def check():
        """
        :ref:`condition` that assures the armature is selected.

        :return: True if the condition is met, else false. String with error message.
        """
        if bpy.context.active_object:
            return (
                bpy.context.active_object.type == "ARMATURE",
                "Model not selected and active.",
            )
        else:
            return False, "No model selected" class ObjectMode(Condition):
    @staticmethod
    def check():
        """
        :term:`condition` that assures that the :term:`object mode` is selected.
        """
        if bpy.context.object:
            return bpy.context.object.mode == "OBJECT", "Must be in object mode"
        else:
            return True, "" def LogFunction(func):
    def func_logger(self, context):
def log_callstack(back_trace=False):
def log_callstack_last(back_trace=False): class SelectSegment(RDOperator):
    """
    :term:`Operator<operator>` for selecting a segment. If :attr:`segment_name` is empty,
    all segments will be deselected
    """

    bl_idname = config.OPERATOR_PREFIX + "select_segment"
    bl_label = "Select Segment"

    segment_name: StringProperty()

    @RDOperator.OperatorLogger
    def execute(self, context):
        if not (
            context.active_object.type == "ARMATURE"
        ):  # or context.active_object.type == 'MESH'
            raise Exception("BoneSelectionException")

        model = bpy.context.active_object

        for b in model.data.bones:
            b.select = False

        # Alternative to do this:
        # mode = context.mode
        # bpy.ops.object.mode_set(mode='EDIT')
        # bpy.ops.armature.select_all(action='DESELECT')
        # bpy.ops.object.mode_set(mode=mode)

        # Second alternative:
        # mode = context.mode
        # bpy.ops.object.mode_set(mode='EDIT')
        # for b in context.selected_bones:
        #     b.select = False
        # bpy.ops.object.mode_set(mode=mode)

        if self.segment_name:
            model.data.bones.active = model.data.bones[self.segment_name]
            model.data.bones.active.select = True
        else:
            model.data.bones.active = None

        return {"FINISHED"}

    @classmethod
    def run(cls, segment_name=""):
        return super().run(**cls.pass_keywords()) class CreateNewSegment(RDOperator):
    """
    :term:`Operator <operator>` for creating new robot segments and add it to the current model.
    If a segment is already selected, the new segment is added as a child segment. A call :class:`SelectSegment`
    before might be necessary.
    """

    bl_idname = config.OPERATOR_PREFIX + "create_segment"
    bl_label = "Create New Segment"

    # model_name = StringProperty()
    segment_name: StringProperty(name="Enter new segment name:")

    # parent_name = StringProperty(default="")

    @classmethod
    def run(cls, segment_name):  # , parent_name=""):
        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected, SingleSegmentSelected)
    def execute(self, context):
        current_mode = bpy.context.object.mode
        selected_segments = [i for i in context.active_object.data.bones if i.select]

        if len(selected_segments):
            parent_name = selected_segments[0].name
        else:
            parent_name = ""

        bpy.ops.object.mode_set(mode="EDIT", toggle=False)
        bone = context.active_object.data.edit_bones.new(self.segment_name)
        segment_name = self.segment_name
        bone.head = (0, 0, 0)  # Dummy
        bone.tail = (0, 0, 1)  # Dummy
        bone.lock = True

        if parent_name:
            self.logger.debug(parent_name)
            bone.parent = context.active_object.data.edit_bones[parent_name]

        bpy.ops.object.mode_set(mode="POSE", toggle=False)

        SelectSegment.run(segment_name=segment_name)

        context.active_bone.RobotDesigner.RD_Bone = True
        # default parent joint name of link
        context.active_bone.RobotDesigner.joint_name = segment_name + "_joint"

        if not parent_name:
            context.active_bone.RobotDesigner.Euler.alpha.value = 90.0
            context.active_bone.RobotDesigner.DH.alpha.value = 90.0

        bpy.ops.pose.constraint_add(type="LIMIT_ROTATION")
        bpy.context.object.pose.bones[segment_name].constraints[
            0
        ].name = "RobotDesignerConstraint"
        bpy.ops.object.mode_set(mode=current_mode, toggle=False)

        self.logger.info(
            "Current mode after: {} ({})".format(bpy.context.object.mode, current_mode)
        )
        self.logger.debug("Segment created. ({} -> .format()".format(parent_name, self.segment_name))

        UpdateSegments.run(recurse=True, segment_name=self.segment_name)

        return {"FINISHED"}

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self) class UpdateSegments(RDOperator):
    """
    :term:`operator` for updating the :term:`robot models` after parameters changed.
    If a :term:`segment` name is given it will proceed recursively.
    """

    bl_idname = config.OPERATOR_PREFIX + "udpate_model"
    bl_label = "Update Model"

    # model_name = StringProperty()
    segment_name: StringProperty(default="")
    recurse: BoolProperty(default=True)

    @RDOperator.Postconditions(ModelSelected)
    @RDOperator.OperatorLogger
    #    @RDOperator.Postconditions(ModelSelected)
    #    @Preconditions(ModelSelected)
    def execute(self, context):
        current_mode = bpy.context.object.mode
        self.logger.debug(
            "UpdateSegments: recurse={}, bone={}".format(
            str(self.recurse),
            str(self.segment_name))
        )

        armature_data_name = context.active_object.data.name

        if self.segment_name:
            segment_name = (
                bpy.data.armatures[armature_data_name].bones[self.segment_name].name
            )  # Isn't this the identity operation??
        else:
            segment_name = bpy.data.armatures[armature_data_name].bones[0].name

        SelectSegment.run(segment_name=self.segment_name)

        if (
            not bpy.data.armatures[armature_data_name]
            .bones[segment_name]
            .RobotDesigner.RD_Bone
        ):
            self.logger.info("Not updated (not a RD segment): {}".format(segment_name))
            return {"FINISHED"}

        bone = bpy.data.armatures[armature_data_name].bones[segment_name]

        # Transforms as per RD spec.
        matrix, joint_matrix = bone.RobotDesigner.getTransform()

        bpy.ops.object.mode_set(mode="EDIT", toggle=False)

        editbone = bpy.data.armatures[armature_data_name].edit_bones[
            bpy.data.armatures[armature_data_name].bones[segment_name].name
        ]
        editbone.use_inherit_rotation = True

        # Express desired matrix in frame of the Armature
        if editbone.parent is not None:
            transform = editbone.parent.matrix.copy()
            matrix = transform @ matrix

        # Adjust bone properties to match RD transform specs.
        # Try to move it around rigidly. Keep length.
        pos = matrix.to_translation()
        axis, roll = _mat3_to_vec_roll(matrix.to_3x3())
        length = editbone.length
        editbone.head = pos  # Changes length.
        editbone.tail = pos + length * axis
        editbone.roll = roll

        bpy.ops.object.mode_set(mode=current_mode, toggle=False)

        # update pose
        bpy.ops.object.mode_set(mode="POSE", toggle=False)
        pose_bone = bpy.context.object.pose.bones[segment_name]
        pose_bone.matrix_basis = joint_matrix

        # Local variables for updating the constraints
        # These refer to the settings pertaining to the RD.
        joint_axis = (
            bpy.data.armatures[armature_data_name]
            .bones[segment_name]
            .RobotDesigner.axis
        )
        min_rot = (
            bpy.data.armatures[armature_data_name]
            .bones[segment_name]
            .RobotDesigner.theta.min
        )
        max_rot = (
            bpy.data.armatures[armature_data_name]
            .bones[segment_name]
            .RobotDesigner.theta.max
        )
        jointMode = (
            bpy.data.armatures[armature_data_name]
            .bones[segment_name]
            .RobotDesigner.jointMode
        )
        jointValue = (
            bpy.data.armatures[armature_data_name]
            .bones[segment_name]
            .RobotDesigner.theta.value
        )
        if jointMode == "REVOLUTE":
            if "RobotDesignerConstraint" not in pose_bone.constraints:
                bpy.ops.pose.constraint_add(type="LIMIT_ROTATION")
                bpy.context.object.pose.bones[segment_name].constraints[
                    0
                ].name = "RobotDesignerConstraint"
            constraint = [
                i for i in pose_bone.constraints if i.type == "LIMIT_ROTATION"
            ][0]
            constraint.name = "RobotDesignerConstraint"
            constraint.owner_space = "LOCAL"
            constraint.use_limit_x = True
            constraint.use_limit_y = True
            constraint.use_limit_z = True
            constraint.min_x = 0.0
            constraint.min_y = 0.0
            constraint.min_z = 0.0
            constraint.max_x = 0.0
            constraint.max_y = 0.0
            constraint.max_z = 0.0
            if joint_axis == "X":
                constraint.min_x = radians(min_rot)
                constraint.max_x = radians(max_rot)
            elif joint_axis == "Y":
                constraint.min_y = radians(min_rot)
                constraint.max_y = radians(max_rot)
            elif joint_axis == "Z":
                constraint.min_z = radians(min_rot)
                constraint.max_z = radians(max_rot)
        elif "RobotDesignerConstraint" in pose_bone.constraints:
            pose_bone.constraints.remove(
                pose_bone.constraints["RobotDesignerConstraint"]
            )

        # -------------------------------------------------------
        bpy.ops.object.mode_set(mode=current_mode, toggle=False)

        children_names = [
            i.name
            for i in bpy.data.armatures[armature_data_name].bones[segment_name].children
        ]
        for child_name in children_names:
            UpdateSegments.run(segment_name=child_name, recurse=self.recurse)

        SelectSegment.run(segment_name=segment_name)

        return {"FINISHED"} class SelectModel(RDOperator):
    """
    :ref:`operator` for ...

    **Preconditions:**

    **RDOperator.Postconditions:**
    """

    bl_idname = config.OPERATOR_PREFIX + "selectarmature"
    bl_label = "Select Model"

    model_name: StringProperty()

    @classmethod
    def run(cls, model_name=""):
        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected)
    def execute(self, context):
        from . import segments

        for obj in context.scene.objects:
            obj.select_set(False)

        context.view_layer.objects.active = bpy.data.objects[self.model_name]
        context.active_object.select_set(True)
        global_properties.model_name.set(context.scene, self.model_name)
        global_properties.old_name.set(context.scene, self.model_name)
        # not so sure if this is needed at all

        if len(context.active_object.data.bones) > 0:
            baseBoneName = context.active_object.data.bones[0].name
            segments.SelectSegment.run(segment_name=baseBoneName)
        return {"FINISHED"} class CreateNewModel(RDOperator):
    """
    :ref:`operator` for creating new robot models.

    **Preconditions:**

    **RDOperator.Postconditions:**
    """

    bl_idname = config.OPERATOR_PREFIX + "create_model"
    bl_label = "Create New Robot Model"

    model_name: StringProperty(name="Enter model name:")
    base_segment_name: StringProperty(name="Enter root segment name:", default="")

    @classmethod
    def run(cls, model_name, base_segment_name):
        """
        Run this operator
        """

        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected)
    def execute(self, context):
        from . import segments

        bots = [obj.name for obj in context.scene.objects if obj.type == "ARMATURE"]
        index = 1
        name = self.model_name
        while self.model_name in bots:
            self.model_name = name + str(index)
            index += 1

        model_data = bpy.data.armatures.new(self.model_name)
        model_object = bpy.data.objects.new(self.model_name, model_data)
        model_object.data = model_data
        model_data.show_names = True
        model_data.show_axes = True
        model_data.display_type = "STICK"
        scene = bpy.context.scene
        scene.collection.objects.link(model_object)
        bpy.data.objects[
            self.model_name
        ].RobotDesigner.modelMeta.model_config = self.model_name
        SelectModel.run(model_name=self.model_name)
        if self.base_segment_name:
            segments.CreateNewSegment.run(segment_name=self.base_segment_name)
        return {"FINISHED"}

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self) class SelectCoordinateFrame(RDOperator):
    """
    :ref:`operator` for selecting mesh for visualization of coordinate frames.

    **Preconditions:**

    **RDOperator.Postconditions:**
    """

    bl_idname = config.OPERATOR_PREFIX + "selectcf"
    bl_label = "Select Mesh"

    mesh_name: StringProperty()

    @classmethod
    def run(cls, mesh_name=""):
        """
        Run this operator
        """

        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected)
    def execute(self, context):
        for bone in [
            i.name for i in bpy.data.armatures[context.active_object.data.name].bones
        ]:
            if self.mesh_name != "None":
                context.active_object.pose.bones[bone].custom_shape = bpy.data.objects[
                    self.mesh_name
                ]
            else:
                context.active_object.pose.bones[bone].custom_shape = None
        return {"FINISHED"} class SelectGeometry(RDOperator):
    """
    :ref:`Operator <operator>` for selecting a geometry (:class:`bpy.types.Object` with `bpy.types.Mesh` data)
    second to the selected model (Blender object with :class:`bpy.types.Armature` data)
    """

    bl_idname = config.OPERATOR_PREFIX + "select_geometry"
    bl_label = "Select Geometry"

    geometry_name: StringProperty()

    @classmethod
    def run(cls, geometry_name=""):
        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected, SingleMeshSelected)
    def execute(self, context):
        mesh = bpy.data.objects[self.geometry_name]

        if mesh.type != "MESH":
            self.report({"ERROR"}, "Object is no geometry (Mesh). Is %s" % mesh.type)
            global_properties.mesh_name.set(context.scene, "Search")
            return {"FINISHED"}

        # Has the side effect of de-selecting all other objects except for the armature and our mesh.
        global_properties.mesh_name.set(context.scene, self.geometry_name)

        #    context.region.tag_redraw()
        #    context.area.tag_redraw()
        return {"FINISHED"} class AssignGeometry(RDOperator):
    """
    :ref:`operator` for assigning a geometry to a segment.
    """

    bl_idname = config.OPERATOR_PREFIX + "assign_geometry"
    bl_label = "Assign Selected Geometry to Active Segment"

    attach_collision_geometry: BoolProperty(
        name="Assign as Collision Mesh",
        description="Adds a collision tag to the mesh",
        default=False,
    )

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected, SingleMeshSelected, SingleSegmentSelected)
    def execute(self, context):
        # Set parenting relation. There are two ways to attach geometry to a bone:
        # This way, which sets the parent_bone variable of the object. Or by using vertex weights,
        # in which case parent_bone should be left empty.
        # See also https://blender.stackexchange.com/questions/9200/make-object-a-a-parent-of-object-b-via-python
        # At this point bpy.context.scene.objects.active should point to the armature which will be the parent.
        bpy.ops.object.parent_set(type="BONE", keep_transform=True)
        # In order to get the child we have to jump through some hoops.
        obj = bpy.data.objects[global_properties.mesh_name.get(context.scene)]

        # Change the name depending on whether we want collision geometry or visual geometry.

        def maybe_remove_prefix(s, prefix):
            return s[len(prefix) :] if s.startswith(prefix) else s

        def maybe_remove_postfix(s, postfix):
            return s[: -len(postfix)] if s.endswith(postfix) else s

        new_name = obj.name
        new_name = maybe_remove_postfix(
            new_name, ".001"
        )  # Heuristic to remove the suffix created by cloning.
        # Heuristics to remove previously assigned prefixes.
        # Since the prefix is regenerated it seems in order to try to remove the old prefix.
        if len(new_name) > len("VIS_"):
            new_name = maybe_remove_prefix(new_name, "VIS_")
        if len(new_name) > len("COL_"):
            new_name = maybe_remove_prefix(new_name, "COL_")

        operator_logger.info(
            "Attaching {} to {}".format("COL" if self.attach_collision_geometry else "VIS",
             obj.name)
        )

        if (
            self.attach_collision_geometry
            and obj.RobotDesigner.tag != "COLLISION"
            and "BASIC_COLLISION_" not in obj.RobotDesigner.tag
        ):
            obj.RobotDesigner.tag = "COLLISION"
            new_name = "COL_" + new_name
        elif (
            obj.RobotDesigner.tag == "COLLISION"
            or "BASIC_COLLISION_" in obj.RobotDesigner.tag
        ):
            pass
        else:
            obj.RobotDesigner.tag = "DEFAULT"
            new_name = "VIS_" + new_name
        obj.name = new_name
        obj.RobotDesigner.fileName = new_name
        # Update the global reference to the selected mesh.
        global_properties.mesh_name.set(context.scene, obj.name)
        # This is just a boolean variable which is reset here to False. It helps
        # determine whether we want a collision mesh or a visual one.

        return {"FINISHED"}

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self, width=400) class AssignPhysical(RDOperator):
    """
    :ref:`operator` for assigning physics frame to active bone

    **Preconditions:**

    **Postconditions:**
    """

    bl_idname = config.OPERATOR_PREFIX + "assignphysicsframe"
    bl_label = "Assign Selected Physics Frame To Active Bone"

    @classmethod
    def run(cls):
        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(
        ModelSelected, SingleSegmentSelected, SingleMassObjectSelected
    )
    def execute(self, context):
        bpy.ops.object.parent_set(type="BONE")

        # if bpy.context.active_bone.parent:
        #     to_parent_matrix = bpy.context.active_bone.parent.matrix_local
        # else:
        #     to_parent_matrix = Matrix()
        # from_parent_matrix, bone_matrix = bpy.context.active_bone.RobotDesigner.getTransform()
        # armature_matrix = bpy.context.active_object.matrix_basis

        # # find selected physics frame
        # for ob in bpy.data.objects:
        #     if ob.select and ob.RobotDesigner.tag == 'PHYSICS_FRAME':
        #         frame = ob
        #         operator_logger.debug(frame.name)

        # frame.matrix_basis = armature_matrix*to_parent_matrix*from_parent_matrix*bone_matrix
        # frame.matrix_basis = parent_matrix*armature_matrix*bone_matrix
        return {"FINISHED"} class CreatePhysical(RDOperator):
    """
    :ref:`operator` for creating a physics frame

    **Preconditions:**

    **Postconditions:**
    """

    bl_idname = config.OPERATOR_PREFIX + "createphysicsframe"
    bl_label = "Create And Attach Physics Frame"

    frameName: StringProperty()

    @classmethod
    def run(cls, frameName=""):
        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected)  # todo condition for physicframe
    def execute(self, context):

        bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

        armature = context.active_object

        bones = set([b for b in armature.data.bones if b.select])

        bones_that_have_physics_frames = set()
        for obj in armature.children:
            if obj.parent_bone and obj.RobotDesigner.tag == "PHYSICS_FRAME":
                bone = armature.data.bones[obj.parent_bone]
                bones_that_have_physics_frames.add(bone)

        bones = bones.difference(bones_that_have_physics_frames)

        frame = None
        for bone in bones:
            frame = just_create_the_physics_frame(context, bone.name)
            assign_the_physics_frame_to_the_bone(context, frame, bone)
            # https://blender.stackexchange.com/questions/15353/get-the-location-in-world-coordinates-of-a-bones-head-and-tail-while-in-pose-mo
            # https: // blender.stackexchange.com / questions / 80306 / location - still - the - same - although - its - moved
            special_bone = armature.pose.bones[bone.name]
            # frame.worldPosition = armature.matrix_world.to_translation() + 0.5 * (bone.active_pose_bone.tail + bone.active_pose_bone.head)
            frame.matrix_world = armature.matrix_world @ special_bone.matrix

        if frame:
            SelectPhysical.run(frameName=frame.name)
            frame.RobotDesigner.dynamics.scale_update(context)

        return {"FINISHED"} class SelectPhysical(RDOperator):
    """
    :ref:`operator` for selecting a physics frame
    **Preconditions:**

    **Postconditions:**
    """

    bl_idname = config.OPERATOR_PREFIX + "selectphysicsframe"
    bl_label = "Select Physics Frame"

    frameName: StringProperty()

    @classmethod
    def run(cls, frameName=""):
        return super().run(**cls.pass_keywords())

    @RDOperator.OperatorLogger
    @RDOperator.Postconditions(ModelSelected)  # todo condition for physicsframe
    def execute(self, context):
        arm = context.active_object

        frame = bpy.data.objects[self.frameName]

        for obj in bpy.data.objects:
            obj.select_set(False)

        frame.select_set(True)
        arm.select_set(True)

        context.view_layer.objects.active = arm

        return {"FINISHED"} def set_value(l):
    def __init__(self, connected_joints=None, connected_links=None, robot=None):
    def parse(file_name):
    def build(self, link, joint=None, depth=0):
    def create_empty(name):
    def write(self, file_name):
    def _write(self):
    def add(self):
    def add_mesh(self, file_name, scale_factor=(1.0, 1.0, 1.0)):
    def add_collision(self, file_name, scale_factor=(1.0, 1.0, 1.0)):
    def add_basic(self, tag, scale_factor=(1.0, 1.0, 1.0)):
    def add_inertial(self):
    def add_camera_sensor(self):
    def set_defaults(self):
    def show(self, depth=0):
class SDFTree(object): def string2float_list(s):
def rounded(val):
def pose_modelpose(pose, model_pose):
def pose_string2homogeneous(pose):
def pose_float2homogeneous(pose_float):
def localpose2globalpose(ref_pose_global, angles, translate):
def pose2origin(parent_pose_homo, self_pose_homo):
def homo2origin(self_pose_homo):
def rpy_to_xyz(rpy):
def convert_euler(euler, old_convention, new_convention):
def list_to_string(l):
def string_to_list(s):
def get_value(element, default=0.0):
def get_list_value(element, default=0.0): class RDSelectedObjects(PropertyGroupHandlerBase):
class RDGlobals(PropertyGroupHandlerBase):
    def __init__(self):
    def debug_level_callback(self, context):
    def updateGlobals(self, context):
    def updateBoneName(self, context):
    def display_physics(self, context):
    def attach_world(self, context):
    def updateMuscleName(self, context):
    def update_geometry_name(self, context):
    def update_wrapping_name(self, context):
    def display_geometries(self, context):
    def display_wrapping_geometries(self, context):
    def display_muscles(self, context):
    def display_sensors(self, context):
    def name_update(self, context):
    def muscle_dim_update(self, context):
    def __init__(self): def CreateFromDocument (xml_text, default_namespace=None, location_base=None):
def CreateFromDOM (node, default_namespace=None):
def _BuildAutomaton_ ():
def _BuildAutomaton_2 ():
def _BuildAutomaton_3 ():
def _BuildAutomaton_4 ():
def _BuildAutomaton_5 ():
def _BuildAutomaton_6 ():
def _BuildAutomaton_7 ():
def _BuildAutomaton_8 ():
def _BuildAutomaton_9 ():
def _BuildAutomaton_10 ():
def _BuildAutomaton_11 ():
def _BuildAutomaton ():
def _BuildAutomaton_12 ():
def _BuildAutomaton_13 ():
def _BuildAutomaton_14 ():
def _BuildAutomaton_15 ():
def _BuildAutomaton_17 ():
def _BuildAutomaton_18 ():
def _BuildAutomaton_16 ():
class maturity_type (pyxb.binding.datatypes.string, pyxb.binding.basis.enumeration_mixin):
class sensor_type (pyxb.binding.datatypes.string, pyxb.binding.basis.enumeration_mixin):
class actuator_type (pyxb.binding.datatypes.string, pyxb.binding.basis.enumeration_mixin):
class ModelConfiguration (pyxb.binding.basis.complexTypeDefinition):
class sdf_versioned (pyxb.binding.basis.complexTypeDefinition):
class author_type (pyxb.binding.basis.complexTypeDefinition):
class documentation_type (pyxb.binding.basis.complexTypeDefinition):
class sensors_type (pyxb.binding.basis.complexTypeDefinition):
class actuators_type (pyxb.binding.basis.complexTypeDefinition):
class youtube_resource (pyxb.binding.basis.complexTypeDefinition):
class url_resource (pyxb.binding.basis.complexTypeDefinition):
class publication_type (pyxb.binding.basis.complexTypeDefinition):
class frontend_skin_model (pyxb.binding.basis.complexTypeDefinition):
class CTD_ANON (pyxb.binding.basis.complexTypeDefinition):
class CTD_ANON_ (pyxb.binding.basis.complexTypeDefinition):
import os
import bpy
        import os
        import os
        import zipfile
        import tempfile
from math import *
from mathutils import Euler, Matrix, Vector
from pathlib import Path
from bpy.props import StringProperty
from ..osim.osim_import import OsimImporter
from ...core import config, PluginManager, Condition, RDOperator
from ...operators.helpers import ModelSelected, ObjectMode
from ...core.logfile import export_logger
from ...operators.segments import SelectSegment, CreateNewSegment, UpdateSegments
from ...operators.model import SelectModel, CreateNewModel, SelectCoordinateFrame
from ...operators.rigid_bodies import SelectGeometry, AssignGeometry
from ...operators.dynamics import AssignPhysical, CreatePhysical, SelectPhysical
from .generic import sdf_tree
from .generic.helpers import (
    string_to_list,
    get_value,
    get_list_value,
    rounded,
    inverse_matrix,
    pose_float2homogeneous,
    pose2origin,
    homo2origin,
    pose_modelpose,
)
from ...properties.globals import global_properties
from .generic import config_model_dom
                self.PACKAGE_URL, ""
            )
            mesh_path = os.path.join(str(Path(self.base_dir).parent), mesh_path)
            export_logger.debug("mesh path {}".format(mesh_path))

        else:
            self.operator.report({"ERROR"}, "Unsupported URL schema")
            export_logger.error("Unsupported URL schema")
            return
        # if len(model.geometry[0].cylinder) > 0:
        #     export_logger.debug('Cylinder Existing')
        #     c_radius = model.geometry[0].cylinder[0].radius[0]
        #     c_depth = model.geometry[0].cylinder[0].length[0]
        #
        #     bpy.ops.mesh.primitive_cylinder_add(depth=c_depth, radius=c_radius)
        #     bpy.context.active_object.RobotDesigner.fileName = os.path.basename(model.name)

        if not os.path.isfile(mesh_path):
            raise Exception("Mesh file {} not found".format(mesh_path))

        fn, extension = os.path.splitext(mesh_path)
        if extension == ".stl" or extension == ".STL":
            try:
                bpy.ops.import_mesh.stl(filepath=mesh_path)
            except:
                pass
        elif extension == ".dae" or extension == ".DAE":
            try:
                bpy.ops.wm.collada_import(filepath=mesh_path)
            except:
                pass

        bpy.context.active_object.RobotDesigner.fileName = os.path.basename(
            os.path.splitext(mesh_path)[0]
        )

        export_logger.debug(
            "Active robot name: {}".format(bpy.context.active_object.RobotDesigner.fileName)
        )
        bpy.context.active_object.name = os.path.basename(model.name)
        model_name = bpy.context.active_object.name
        # bpy.context.active_object.type = 'ARMATURE'
        model_type = bpy.context.active_object.type

        export_logger.debug("model_name (geometry): {}".format(model_name))
        export_logger.debug("model_type (geometry): {}".format(model_type))

        export_logger.debug(
            "model_geometry_mesh_scale (geometry): {}".format(model.geometry[0].mesh[0].scale)
        )

        scale_matrix = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
        # scale_matrix = Matrix([[scale_factor[0], 0, 0, 0], [0, scale_factor[1], 0, 0],
        #                       [0, 0, scale_factor[2], 0], [0, 0, 0, 1]])

        # todo: if geometry pose is missing
        if not model.pose:
            model_posexyz = [0, 0, 0]
            model_poserpy = [0, 0, 0]
        else:
            export_logger.debug("model_pose (geometry): {}".format(model.pose[0].value()))
            model_posexyz = string_to_list(model.pose[0].value())[0:3]
            model_poserpy = string_to_list(model.pose[0].value())[3:]

        return (
            Matrix.Translation(Vector(model_posexyz))
            @ Euler(model_poserpy, "XYZ").to_matrix().to_4x4()
            @ scale_matrix
        )

    def parse(self, node: sdf_tree.SDFTree, ref_pose, parent_name=""):
        """
        Recursively parses the SDF tree elements.

        :param node: The actual segment
        :param parent_name: Name of the parent segment (if None the segment is a root element)
        :param ref_pose: Reference pose from parent link (SDF, pose referenced to a global coordinate)

        """

        C = bpy.context

        export_logger.info("Context mode: %s", C.mode)
        export_logger.info("parent name: %s", parent_name)

        export_logger.debug(
            "active object name : %s", C.active_object.name
        )  # the name of the robot

        export_logger.debug("active object type : {}".format(C.active_object.type))

        if bpy.context.active_object:
            export_logger.debug(
                "active object type == Armature: {}, {}".format(
                bpy.context.active_object.type == "ARMATURE",
                "Model not selected and active.")
            )
        else:
            export_logger.debug(
                "active object type == Armature: %s, %s", False, "No model selected"
            )

        SelectSegment.run(segment_name=parent_name)

        CreateNewSegment.run(segment_name=node.link.name)

        segment_name = C.active_bone.name

        export_logger.info("%s -> %s", parent_name, segment_name)
        export_logger.info("link name -> %s", node.link.name)

        # to confirm: SDF a joint's pose is given in child link frame, URDF joint frame = child link frame

        if not node.link.pose:
            child_link_pose = [0, 0, 0, 0, 0, 0]
        else:
            child_link_pose = string_to_list(
                get_value(node.link.pose[0].value(), "0 0 0 0 0 0")
            )


        parent_pose_homo = pose_float2homogeneous(rounded(ref_pose))